@sap/cds-compiler 4.1.2 → 4.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +101 -1
- package/bin/cdsc.js +6 -3
- package/doc/CHANGELOG_BETA.md +5 -0
- package/doc/CHANGELOG_DEPRECATED.md +15 -0
- package/lib/api/main.js +2 -2
- package/lib/api/options.js +2 -2
- package/lib/api/validate.js +24 -24
- package/lib/base/message-registry.js +41 -6
- package/lib/base/messages.js +7 -0
- package/lib/base/model.js +37 -8
- package/lib/checks/elements.js +11 -10
- package/lib/checks/manyNavigations.js +33 -0
- package/lib/checks/onConditions.js +5 -2
- package/lib/checks/queryNoDbArtifacts.js +2 -3
- package/lib/checks/selectItems.js +4 -55
- package/lib/checks/utils.js +3 -2
- package/lib/checks/validator.js +3 -1
- package/lib/compiler/.eslintrc.json +2 -1
- package/lib/compiler/assert-consistency.js +27 -24
- package/lib/compiler/base.js +6 -2
- package/lib/compiler/builtins.js +34 -34
- package/lib/compiler/checks.js +179 -208
- package/lib/compiler/classes.js +2 -2
- package/lib/compiler/cycle-detector.js +6 -6
- package/lib/compiler/define.js +66 -45
- package/lib/compiler/extend.js +81 -72
- package/lib/compiler/finalize-parse-cdl.js +26 -26
- package/lib/compiler/generate.js +61 -45
- package/lib/compiler/index.js +47 -49
- package/lib/compiler/kick-start.js +8 -7
- package/lib/compiler/moduleLayers.js +1 -1
- package/lib/compiler/populate.js +42 -35
- package/lib/compiler/propagator.js +6 -6
- package/lib/compiler/resolve.js +170 -126
- package/lib/compiler/shared.js +122 -45
- package/lib/compiler/tweak-assocs.js +93 -40
- package/lib/compiler/utils.js +15 -12
- package/lib/edm/.eslintrc.json +40 -1
- package/lib/edm/annotations/genericTranslation.js +721 -707
- package/lib/edm/annotations/preprocessAnnotations.js +88 -77
- package/lib/edm/csn2edm.js +389 -378
- package/lib/edm/edm.js +678 -772
- package/lib/edm/edmAnnoPreprocessor.js +132 -146
- package/lib/edm/edmInboundChecks.js +29 -27
- package/lib/edm/edmPreprocessor.js +686 -646
- package/lib/edm/edmUtils.js +277 -296
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +1 -1
- package/lib/gen/languageParser.js +1253 -1276
- package/lib/json/from-csn.js +34 -4
- package/lib/json/to-csn.js +4 -4
- package/lib/language/language.g4 +2 -5
- package/lib/main.d.ts +61 -1
- package/lib/model/csnUtils.js +31 -2
- package/lib/model/revealInternalProperties.js +1 -1
- package/lib/modelCompare/compare.js +37 -2
- package/lib/modelCompare/utils/filter.js +1 -1
- package/lib/optionProcessor.js +15 -3
- package/lib/render/toCdl.js +30 -4
- package/lib/render/toSql.js +5 -9
- package/lib/render/utils/common.js +8 -6
- package/lib/transform/db/applyTransformations.js +1 -1
- package/lib/transform/db/cdsPersistence.js +1 -1
- package/lib/transform/db/constraints.js +47 -17
- package/lib/transform/db/expansion.js +121 -47
- package/lib/transform/db/flattening.js +75 -7
- package/lib/transform/forOdata.js +4 -1
- package/lib/transform/forRelationalDB.js +80 -62
- package/lib/transform/localized.js +91 -54
- package/lib/transform/transformUtils.js +9 -10
- package/lib/utils/file.js +7 -7
- package/lib/utils/moduleResolve.js +210 -121
- package/lib/utils/objectUtils.js +1 -1
- package/package.json +5 -5
package/lib/json/from-csn.js
CHANGED
|
@@ -72,8 +72,9 @@
|
|
|
72
72
|
* What "kind" values are possible in a definition. The root "definitions" properties allows
|
|
73
73
|
* more kinds than e.g. definitions inside "elements".
|
|
74
74
|
*
|
|
75
|
-
* @property {string|string[]} [onlyWith]
|
|
76
|
-
* Defines that the property *must* be used with these properties.
|
|
75
|
+
* @property {string|string[]|Object} [onlyWith]
|
|
76
|
+
* Defines that the property *must* be used with one of these properties.
|
|
77
|
+
* If an object, it maps the kind value to a string or array of strings.
|
|
77
78
|
*
|
|
78
79
|
* @property {number} [minLength]
|
|
79
80
|
* Minimum number of elements that an array must have.
|
|
@@ -255,12 +256,14 @@ const schema = compileSchema( {
|
|
|
255
256
|
requires: [ 'extend', 'annotate' ],
|
|
256
257
|
},
|
|
257
258
|
enum: {
|
|
259
|
+
type: enumDict,
|
|
258
260
|
dictionaryOf: definition,
|
|
259
261
|
defaultKind: 'enum',
|
|
260
262
|
validKinds: [ 'enum' ],
|
|
261
263
|
inKind: [ 'element', 'type', 'param', 'annotation', 'annotate', 'extend' ],
|
|
262
264
|
},
|
|
263
265
|
elements: {
|
|
266
|
+
type: elementsDict,
|
|
264
267
|
dictionaryOf: definition,
|
|
265
268
|
defaultKind: 'element',
|
|
266
269
|
validKinds: [ 'element' ],
|
|
@@ -280,6 +283,7 @@ const schema = compileSchema( {
|
|
|
280
283
|
dictionaryOf: actions,
|
|
281
284
|
defaultKind: 'action',
|
|
282
285
|
validKinds: [ 'action', 'function' ],
|
|
286
|
+
onlyWith: { aspect: 'elements' },
|
|
283
287
|
inKind: [ 'entity', 'aspect', 'annotate', 'extend' ],
|
|
284
288
|
},
|
|
285
289
|
params: {
|
|
@@ -656,7 +660,7 @@ const schema = compileSchema( {
|
|
|
656
660
|
'-expr': { // '-expr' and '-' must not exist top-level
|
|
657
661
|
prop: '@‹anno›',
|
|
658
662
|
type: object,
|
|
659
|
-
optional: [ '=', '#', 'xpr', 'ref', 'val', 'list', 'literal', 'func', 'args' ],
|
|
663
|
+
optional: [ '=', '#', 'xpr', 'ref', 'val', 'list', 'literal', 'func', 'args', 'param' ],
|
|
660
664
|
schema: {
|
|
661
665
|
'=': {
|
|
662
666
|
type: renameTo( '$tokenTexts', string ),
|
|
@@ -1038,7 +1042,7 @@ function definition( def, spec, xsn, csn, name ) {
|
|
|
1038
1042
|
// for an 'annotate', both 'annotate' and the "host" kind must be expected
|
|
1039
1043
|
(!inExtensions || s.inKind.includes( inExtensions ) ||
|
|
1040
1044
|
// extending elements in returns can be without 'returns' in CSN
|
|
1041
|
-
// TODO:
|
|
1045
|
+
// see function elementsDict() for detail, TODO: remove finally
|
|
1042
1046
|
inExtensions === 'action' && p === 'elements');
|
|
1043
1047
|
}
|
|
1044
1048
|
}
|
|
@@ -1121,6 +1125,27 @@ function returnsDefinition( def, spec, xsn, csn ) {
|
|
|
1121
1125
|
return definition( def, spec, xsn, csn, '' );
|
|
1122
1126
|
}
|
|
1123
1127
|
|
|
1128
|
+
// Temporary function as long as the message below is not a hard error
|
|
1129
|
+
function elementsDict( def, spec, xsn ) {
|
|
1130
|
+
const elements = dictionaryOf( definition )( def, spec );
|
|
1131
|
+
if (inExtensions !== 'action')
|
|
1132
|
+
return elements;
|
|
1133
|
+
warning( 'syntax-expecting-returns', elements[$location],
|
|
1134
|
+
{ prop: 'elements', parentprop: 'returns' },
|
|
1135
|
+
// eslint-disable-next-line max-len
|
|
1136
|
+
'Expecting property $(PROP) to be put into an object for property $(PARENTPROP) when annotating action return structures' );
|
|
1137
|
+
xsn.returns = { kind: 'annotate', elements, location: elements[$location] };
|
|
1138
|
+
return undefined;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
function enumDict( def, spec, xsn ) {
|
|
1142
|
+
const dict = dictionaryOf( definition )( def, spec );
|
|
1143
|
+
if (!inExtensions)
|
|
1144
|
+
return dict;
|
|
1145
|
+
xsn.elements = dict; // normalize to `elements` for `annotate`
|
|
1146
|
+
return undefined;
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1124
1149
|
// For v1 CSNs with annotation definitions
|
|
1125
1150
|
function attachVocabInDefinitions( csn ) {
|
|
1126
1151
|
if (!csn.vocabularies) {
|
|
@@ -1827,6 +1852,11 @@ function calculateKind( def, spec ) {
|
|
|
1827
1852
|
function onlyWith( spec, need, csn, prop, xor, expected ) {
|
|
1828
1853
|
if (!need)
|
|
1829
1854
|
return spec;
|
|
1855
|
+
if (typeof need === 'object' && !Array.isArray( need )) {
|
|
1856
|
+
need = need[csn.kind];
|
|
1857
|
+
if (!need)
|
|
1858
|
+
return spec;
|
|
1859
|
+
}
|
|
1830
1860
|
if (typeof need === 'string') {
|
|
1831
1861
|
if (need in csn) // TODO: enumerable ?
|
|
1832
1862
|
return spec;
|
package/lib/json/to-csn.js
CHANGED
|
@@ -115,7 +115,6 @@ const transformers = {
|
|
|
115
115
|
offset: expression,
|
|
116
116
|
on: onCondition,
|
|
117
117
|
// definitions, extensions, members ----------------------------------------
|
|
118
|
-
returns, // storing the return type of actions
|
|
119
118
|
notNull: value,
|
|
120
119
|
default: expression,
|
|
121
120
|
// targetElement: ignore, // special display of foreign key, renameTo: select
|
|
@@ -123,6 +122,7 @@ const transformers = {
|
|
|
123
122
|
query,
|
|
124
123
|
elements,
|
|
125
124
|
actions, // TODO: just normal dictionary
|
|
125
|
+
returns, // storing the return type of actions
|
|
126
126
|
// special: top-level, cardinality -----------------------------------------
|
|
127
127
|
sources,
|
|
128
128
|
definitions: sortedDict,
|
|
@@ -465,13 +465,13 @@ function attachAnnotations( annotate, prop, dict, inferred, insideReturns = fals
|
|
|
465
465
|
attachAnnotations( sub, 'actions', entry.actions, inf );
|
|
466
466
|
else if (entry.params)
|
|
467
467
|
attachAnnotations( sub, 'params', entry.params, inf );
|
|
468
|
-
const obj = entry.returns || entry;
|
|
468
|
+
const obj = entry.returns || entry;
|
|
469
469
|
const many = obj.items || obj;
|
|
470
470
|
const elems = (many.targetAspect || many).elements;
|
|
471
471
|
if (elems)
|
|
472
472
|
attachAnnotations( sub, 'elements', elems, inf, entry.returns );
|
|
473
|
-
if (many.enum)
|
|
474
|
-
attachAnnotations( sub, '
|
|
473
|
+
else if (many.enum) // make 'enum' annotations appear in 'elements' annotate
|
|
474
|
+
attachAnnotations( sub, 'elements', many.enum, inf, entry.returns );
|
|
475
475
|
}
|
|
476
476
|
if (Object.keys( sub ).length)
|
|
477
477
|
annoDict[name] = sub;
|
package/lib/language/language.g4
CHANGED
|
@@ -2010,11 +2010,8 @@ orderByClause[ inQuery ] returns [ query ]
|
|
|
2010
2010
|
limitClause[ inQuery ] returns [ query ]
|
|
2011
2011
|
:
|
|
2012
2012
|
limkw=LIMIT { $query = this.unaryOpForParens( $inQuery, '$'+'query' ); }
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
literal: 'null', val: null, location: this.tokenLocation($limnull) } }; }
|
|
2016
|
-
)
|
|
2017
|
-
( OFFSET off=Number { $query.limit.offset = this.numberLiteral( $off ); } )? // unsigned integer
|
|
2013
|
+
lim=expression { $query.limit = { rows: $lim.expr }; }
|
|
2014
|
+
( OFFSET off=expression { $query.limit.offset = $off.expr; } )? // unsigned integer
|
|
2018
2015
|
;
|
|
2019
2016
|
|
|
2020
2017
|
orderBySpec returns[ ob ]
|
package/lib/main.d.ts
CHANGED
|
@@ -70,6 +70,18 @@ declare namespace compiler {
|
|
|
70
70
|
* - universal : In development (BETA)
|
|
71
71
|
*/
|
|
72
72
|
csnFlavor?: string | 'client' | 'gensrc' | 'universal'
|
|
73
|
+
/**
|
|
74
|
+
* If set, backends will not create localized convenience views for those views,
|
|
75
|
+
* that only have an association to a localized entity/view. Views will only get
|
|
76
|
+
* a convenience view, if they themselves contain localized elements (i.e. either
|
|
77
|
+
* have simple projection on localized elements and CDL-casts to a localized element).
|
|
78
|
+
*
|
|
79
|
+
* The OData backend will not set `$localized: true` markers for such cases.
|
|
80
|
+
*
|
|
81
|
+
* Does not work for backends to.hdi(), to.hdbcds() or to.sql() with `sqlDialect: 'hana'`,
|
|
82
|
+
* since in all those dialects, associations still exist in generated artifacts.
|
|
83
|
+
*/
|
|
84
|
+
fewerLocalizedViews?: boolean
|
|
73
85
|
}
|
|
74
86
|
|
|
75
87
|
/**
|
|
@@ -86,6 +98,30 @@ declare namespace compiler {
|
|
|
86
98
|
* the prefix for SAP CDS packages / CDS files.
|
|
87
99
|
*/
|
|
88
100
|
cdsHome?: string
|
|
101
|
+
/**
|
|
102
|
+
* "Doc comments" (documentation comments) are those comments starting with `/**` in CDL
|
|
103
|
+
* or the `doc` property in CSN. This option is an _output_ option, which can have three
|
|
104
|
+
* values:
|
|
105
|
+
*
|
|
106
|
+
* - `true`:
|
|
107
|
+
* Doc comments will appear in the compiled CSN. Basic sanity checks are performed:
|
|
108
|
+
* In CDL, if a doc comment appears at a not-defined position, where it has no impact,
|
|
109
|
+
* an info message is emitted. For CSN input, it is checked that the `doc` property
|
|
110
|
+
* is a string or `null`.
|
|
111
|
+
*
|
|
112
|
+
* - `false`:
|
|
113
|
+
* Doc comments will not be parsed for CDL, and will be stripped from input CSN,
|
|
114
|
+
* i.e. the compiled CSN (output) does not contain `doc` properties. No checks
|
|
115
|
+
* are performed on doc comments.
|
|
116
|
+
*
|
|
117
|
+
* - `undefined`:
|
|
118
|
+
* Doc comments are checked (see value `true`). For CDL, doc comments are not parsed,
|
|
119
|
+
* i.e. will not appear in the compiled CSN (output).
|
|
120
|
+
* For CSN input, all `doc` properties remain in the CSN.
|
|
121
|
+
*
|
|
122
|
+
* The CDL equivalent of the CSN value `doc: null`, is an empty doc comment.
|
|
123
|
+
*/
|
|
124
|
+
docComment?: boolean
|
|
89
125
|
/**
|
|
90
126
|
* When set to `true`, and the model contains an entity `sap.common.Languages`
|
|
91
127
|
* with an element `code`, all generated texts entities additionally contain
|
|
@@ -95,6 +131,17 @@ declare namespace compiler {
|
|
|
95
131
|
* @since v2.8.0
|
|
96
132
|
*/
|
|
97
133
|
addTextsLanguageAssoc?: boolean
|
|
134
|
+
/**
|
|
135
|
+
* An array of directory names that are used for CDS module lookups.
|
|
136
|
+
* Lookup directory `node_modules/` is appended if not set explicitly.
|
|
137
|
+
*
|
|
138
|
+
* All directories in this array follow the same lookup-pattern as `node_modules/`.
|
|
139
|
+
*
|
|
140
|
+
* See <https://cap.cloud.sap/docs/cds/cdl#model-resolution>
|
|
141
|
+
*
|
|
142
|
+
* @since v4.2.0
|
|
143
|
+
*/
|
|
144
|
+
moduleLookupDirectories?: string[]
|
|
98
145
|
/**
|
|
99
146
|
* Option for {@link compileSources}. If set, all objects inside the
|
|
100
147
|
* provided sources dictionary are interpreted as XSN structures instead
|
|
@@ -468,9 +515,10 @@ declare namespace compiler {
|
|
|
468
515
|
constructor(messages: CompileMessage[], model?: any, text?: string, ...args: any[]);
|
|
469
516
|
/**
|
|
470
517
|
* String to identify this class. Can be used instead of relying on `instanceof`.
|
|
518
|
+
* Always `ERR_CDS_COMPILATION_FAILURE`.
|
|
471
519
|
* @since v4.0.0
|
|
472
520
|
*/
|
|
473
|
-
code
|
|
521
|
+
code: string;
|
|
474
522
|
messages: CompileMessage[];
|
|
475
523
|
toString(): string;
|
|
476
524
|
/**
|
|
@@ -803,6 +851,9 @@ declare namespace compiler {
|
|
|
803
851
|
* identifier in brackets.
|
|
804
852
|
* Otherwise, return the name without brackets.
|
|
805
853
|
*
|
|
854
|
+
* NOTE: If `name` contains newline characters, the resulting delimited identifier
|
|
855
|
+
* will not be parsable by the compiler!
|
|
856
|
+
*
|
|
806
857
|
* Example:
|
|
807
858
|
* ```js
|
|
808
859
|
* to.cdl.smartId('with ![brackets]')
|
|
@@ -817,6 +868,9 @@ declare namespace compiler {
|
|
|
817
868
|
*
|
|
818
869
|
* @param name
|
|
819
870
|
* @param [insideFunction=null]
|
|
871
|
+
* Inside special functions such as SAP HANA's `OCCURRENCES_REGEXPR`, there are more
|
|
872
|
+
* keywords than in other places. Set this value to a function name, if you want to
|
|
873
|
+
* handle those additional keywords as well.
|
|
820
874
|
*/
|
|
821
875
|
function smartId(name: string, insideFunction?: string|null) : string;
|
|
822
876
|
/**
|
|
@@ -824,6 +878,9 @@ declare namespace compiler {
|
|
|
824
878
|
* function identifier in brackets for CDL.
|
|
825
879
|
* Otherwise, return the function name without brackets.
|
|
826
880
|
*
|
|
881
|
+
* NOTE: If `name` contains newline characters, the resulting delimited identifier
|
|
882
|
+
* will not be parsable by the compiler!
|
|
883
|
+
*
|
|
827
884
|
* Example:
|
|
828
885
|
* ```js
|
|
829
886
|
* to.cdl.smartFunctionId('with ![brackets]')
|
|
@@ -839,6 +896,9 @@ declare namespace compiler {
|
|
|
839
896
|
* Escapes the given name according to the CDL language and puts it
|
|
840
897
|
* into `![` and `]`, properly escaping all `]` in the identifier.
|
|
841
898
|
*
|
|
899
|
+
* NOTE: If `name` contains newline characters, the resulting delimited identifier
|
|
900
|
+
* will not be parsable by the compiler!
|
|
901
|
+
*
|
|
842
902
|
* Example:
|
|
843
903
|
* ```js
|
|
844
904
|
* to.cdl.delimitedId('with ![brackets]')
|
package/lib/model/csnUtils.js
CHANGED
|
@@ -1081,6 +1081,9 @@ function getLastPartOfRef( ref ) {
|
|
|
1081
1081
|
*
|
|
1082
1082
|
* Overwrite existing ones only if 'overwrite' is true.
|
|
1083
1083
|
*
|
|
1084
|
+
* IMPORTANT: Consider using copyAnnotationsAndDoc() instead!
|
|
1085
|
+
* Don't forget about doc comments!
|
|
1086
|
+
*
|
|
1084
1087
|
* @param {object} fromNode
|
|
1085
1088
|
* @param {object} toNode
|
|
1086
1089
|
* @param {boolean} [overwrite]
|
|
@@ -1124,6 +1127,32 @@ function copyAnnotationsAndDoc( fromNode, toNode, overwrite = false ) {
|
|
|
1124
1127
|
}
|
|
1125
1128
|
}
|
|
1126
1129
|
|
|
1130
|
+
/**
|
|
1131
|
+
* Same as `copyAnnotationsAndDoc()` but deletes the annotations on source
|
|
1132
|
+
* side after copying them. Useful when applying annotations from `cds.extensions`.
|
|
1133
|
+
*
|
|
1134
|
+
* Overwrite existing ones only if 'overwrite' is true.
|
|
1135
|
+
*
|
|
1136
|
+
* @param {object} sourceNode
|
|
1137
|
+
* @param {object} targetNode
|
|
1138
|
+
* @param {boolean} [overwrite]
|
|
1139
|
+
*/
|
|
1140
|
+
function moveAnnotationsAndDoc( sourceNode, targetNode, overwrite = false ) {
|
|
1141
|
+
// Ignore if no targetNode (in case of errors)
|
|
1142
|
+
if (!targetNode)
|
|
1143
|
+
return;
|
|
1144
|
+
|
|
1145
|
+
const annotations = Object.keys(sourceNode)
|
|
1146
|
+
.filter(key => key.startsWith('@') || key === 'doc');
|
|
1147
|
+
|
|
1148
|
+
for (const anno of annotations) {
|
|
1149
|
+
if (targetNode[anno] === undefined || overwrite) {
|
|
1150
|
+
targetNode[anno] = sourceNode[anno];
|
|
1151
|
+
delete sourceNode[anno];
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1127
1156
|
/**
|
|
1128
1157
|
* Applies annotations from `csn.extensions` to definitions and their elements.
|
|
1129
1158
|
*
|
|
@@ -1151,7 +1180,7 @@ function applyAnnotationsFromExtensions( csn, config ) {
|
|
|
1151
1180
|
if (name && filter(name)) {
|
|
1152
1181
|
const def = csn.definitions[name];
|
|
1153
1182
|
if (def) {
|
|
1154
|
-
|
|
1183
|
+
moveAnnotationsAndDoc(ext, def, config.override);
|
|
1155
1184
|
applyAnnotationsToElements(ext, def);
|
|
1156
1185
|
}
|
|
1157
1186
|
else if (config.notFound) {
|
|
@@ -1173,7 +1202,7 @@ function applyAnnotationsFromExtensions( csn, config ) {
|
|
|
1173
1202
|
forEach(ext.elements, (key, sourceElem) => {
|
|
1174
1203
|
const targetElem = def.elements[key];
|
|
1175
1204
|
if (targetElem) {
|
|
1176
|
-
|
|
1205
|
+
moveAnnotationsAndDoc(sourceElem, targetElem, config.override);
|
|
1177
1206
|
applyAnnotationsToElements(sourceElem, targetElem);
|
|
1178
1207
|
}
|
|
1179
1208
|
});
|
|
@@ -17,7 +17,7 @@ const { CompilerAssertion } = require('../base/error');
|
|
|
17
17
|
const $inferred = Symbol.for('cds.$inferred');
|
|
18
18
|
const $location = Symbol.for('cds.$location');
|
|
19
19
|
|
|
20
|
-
class NOT_A_DICTIONARY {} // used for
|
|
20
|
+
class NOT_A_DICTIONARY {} // used for console.log display
|
|
21
21
|
|
|
22
22
|
function locationString( loc ) {
|
|
23
23
|
if (Array.isArray(loc))
|
|
@@ -249,7 +249,42 @@ function getElementComparator(otherArtifact, addedElementsDict = null, changedEl
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
function relevantTypeChange(type, otherType) {
|
|
252
|
-
return otherType !== type && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
|
|
252
|
+
return otherType !== type && !isSuperflousHanaTypeChange(otherType, type) && ![type, otherType].every(t => ['cds.Association', 'cds.Composition'].includes(t));
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const superflousTypeChanges = {
|
|
256
|
+
// turn it into a real dict
|
|
257
|
+
__proto__: null,
|
|
258
|
+
// We used to put these types into the CSN, although they are just internal
|
|
259
|
+
// so we need to be robust against them now.
|
|
260
|
+
'cds.UTCDateTime': 'cds.DateTime',
|
|
261
|
+
'cds.UTCTimestamp': 'cds.Timestamp',
|
|
262
|
+
'cds.LocalDate': 'cds.Date',
|
|
263
|
+
'cds.LocalTime': 'cds.Time' ,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* We removed some old SAP HANA types from the CSN and we now need to not
|
|
268
|
+
* detect them as changes.
|
|
269
|
+
*
|
|
270
|
+
* @param {string} before Type before
|
|
271
|
+
* @param {string} after Type after
|
|
272
|
+
* @returns {boolean}
|
|
273
|
+
*/
|
|
274
|
+
function isSuperflousHanaTypeChange( before, after ) {
|
|
275
|
+
return superflousTypeChanges[before] ? superflousTypeChanges[before] === after : false;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* If the element has one of the superflous types, do the change so we don't accidentally
|
|
280
|
+
* pass such an old type into the SQL renderer.
|
|
281
|
+
* @param {CSN.Element} element
|
|
282
|
+
* @returns {CSN.Element}
|
|
283
|
+
*/
|
|
284
|
+
function remapType( element ) {
|
|
285
|
+
if(element?.type && superflousTypeChanges[element.type])
|
|
286
|
+
element.type = superflousTypeChanges[element.type];
|
|
287
|
+
return element;
|
|
253
288
|
}
|
|
254
289
|
|
|
255
290
|
/**
|
|
@@ -310,7 +345,7 @@ function addedConstraint(entity, constraint, constraintName, constraintType) {
|
|
|
310
345
|
|
|
311
346
|
function changedElement(element, otherElement) {
|
|
312
347
|
return {
|
|
313
|
-
old: otherElement,
|
|
348
|
+
old: remapType(otherElement),
|
|
314
349
|
new: element
|
|
315
350
|
};
|
|
316
351
|
}
|
|
@@ -159,7 +159,7 @@ function filterCsn( csn ) {
|
|
|
159
159
|
},
|
|
160
160
|
}, [ (artifact) => {
|
|
161
161
|
forEachKey(artifact, (key) => {
|
|
162
|
-
if (key.startsWith('@') && !annosToKeep[key])
|
|
162
|
+
if (key.startsWith('@') && !key.startsWith('@cds.persistence.') && !annosToKeep[key])
|
|
163
163
|
delete artifact[key];
|
|
164
164
|
});
|
|
165
165
|
} ]);
|
package/lib/optionProcessor.js
CHANGED
|
@@ -21,7 +21,7 @@ optionProcessor
|
|
|
21
21
|
.option(' --color <mode>', ['auto', 'always', 'never'])
|
|
22
22
|
.option('-o, --out <dir>')
|
|
23
23
|
.option(' --cds-home <dir>')
|
|
24
|
-
.option(' --
|
|
24
|
+
.option(' --module-lookup-directories <list>')
|
|
25
25
|
.option(' --trace-fs')
|
|
26
26
|
.option(' --error <id-list>')
|
|
27
27
|
.option(' --warn <id-list>')
|
|
@@ -78,7 +78,8 @@ optionProcessor
|
|
|
78
78
|
never:
|
|
79
79
|
-o, --out <dir> Place generated files in directory <dir>, default is "-" for <stdout>
|
|
80
80
|
--cds-home <dir> When set, modules starting with '@sap/cds/' are searched in <dir>
|
|
81
|
-
--
|
|
81
|
+
--module-lookup-directories <list> Comma separated list of directories to look
|
|
82
|
+
for CDS modules. Default is 'node_modules/'.
|
|
82
83
|
-- Indicate the end of options (helpful if source names start with "-")
|
|
83
84
|
|
|
84
85
|
Type options
|
|
@@ -228,6 +229,7 @@ optionProcessor.command('O, toOdata')
|
|
|
228
229
|
.option('-f, --odata-format <format>', ['flat', 'structured'])
|
|
229
230
|
.option('-n, --sql-mapping <style>', ['plain', 'quoted', 'hdbcds'], { aliases: [ '--names' ] })
|
|
230
231
|
.option('-s, --service-names <list>')
|
|
232
|
+
.option(' --fewer-localized-views')
|
|
231
233
|
.help(`
|
|
232
234
|
Usage: cdsc toOdata [options] <files...>
|
|
233
235
|
|
|
@@ -264,6 +266,8 @@ optionProcessor.command('O, toOdata')
|
|
|
264
266
|
source (like "quoted", but using element names with dots)
|
|
265
267
|
-s, --service-names <list> List of comma-separated service names to be rendered
|
|
266
268
|
(default) empty, all services are rendered
|
|
269
|
+
--fewer-localized-views If set, the backends will not create localized convenience views for
|
|
270
|
+
those views, that only have an association to a localized entity/view.
|
|
267
271
|
`);
|
|
268
272
|
|
|
269
273
|
optionProcessor.command('C, toCdl')
|
|
@@ -296,6 +300,7 @@ optionProcessor.command('Q, toSql')
|
|
|
296
300
|
.option(' --disable-hana-comments')
|
|
297
301
|
.option(' --generated-by-comment')
|
|
298
302
|
.option(' --better-sqlite-session-variables')
|
|
303
|
+
.option(' --fewer-localized-views')
|
|
299
304
|
.help(`
|
|
300
305
|
Usage: cdsc toSql [options] <files...>
|
|
301
306
|
|
|
@@ -347,7 +352,10 @@ optionProcessor.command('Q, toSql')
|
|
|
347
352
|
--pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
|
|
348
353
|
--disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
|
|
349
354
|
--generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
|
|
350
|
-
--better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only
|
|
355
|
+
--better-sqlite-session-variables Enable better-sqlite compatible rendering of $user. Only
|
|
356
|
+
active if sqlDialect is \`sqlite\`
|
|
357
|
+
--fewer-localized-views If set, the backends will not create localized convenience views for
|
|
358
|
+
those views, that only have an association to a localized entity/view.
|
|
351
359
|
|
|
352
360
|
`);
|
|
353
361
|
|
|
@@ -425,6 +433,7 @@ optionProcessor.command('toCsn')
|
|
|
425
433
|
.option(' --with-localized')
|
|
426
434
|
.option(' --with-locations')
|
|
427
435
|
.option(' --struct-xpr')
|
|
436
|
+
.option(' --fewer-localized-views')
|
|
428
437
|
.help(`
|
|
429
438
|
Usage: cdsc toCsn [options] <files...>
|
|
430
439
|
|
|
@@ -441,6 +450,9 @@ optionProcessor.command('toCsn')
|
|
|
441
450
|
universal: in development (BETA)
|
|
442
451
|
--with-locations Add $location to CSN artifacts. In contrast to \`--enrich-csn\`,
|
|
443
452
|
$location is an object with 'file', 'line' and 'col' properties.
|
|
453
|
+
--fewer-localized-views If --with-locations and this option are set, the backends
|
|
454
|
+
will not create localized convenience views for those views,
|
|
455
|
+
that only have an association to a localized entity/view.
|
|
444
456
|
|
|
445
457
|
Internal options (for testing only, may be changed/removed at any time)
|
|
446
458
|
--with-localized Add localized convenience views to the CSN output.
|
package/lib/render/toCdl.js
CHANGED
|
@@ -303,6 +303,19 @@ function csnToCdl( csn, options ) {
|
|
|
303
303
|
* @return {string}
|
|
304
304
|
*/
|
|
305
305
|
function renderAnnotateStatement( ext, env ) {
|
|
306
|
+
// Special case: Super annotate has both "returns" and "elements".
|
|
307
|
+
// Render as separate `annotate`s, but keep the order.
|
|
308
|
+
if (ext.elements && ext.returns) {
|
|
309
|
+
const [ , second ] = Object.keys(ext).filter(key => key === 'elements' || key === 'returns');
|
|
310
|
+
|
|
311
|
+
// The first of 'elements' or 'returns' gets all other properties as well.
|
|
312
|
+
// The second only gets one property (itself).
|
|
313
|
+
let result = renderAnnotateStatement({ ...ext, [second]: undefined }, env);
|
|
314
|
+
result += renderAnnotateStatement({ annotate: ext.annotate, [second]: ext[second] }, env);
|
|
315
|
+
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
|
|
306
319
|
// Top-level annotations of the artifact
|
|
307
320
|
let result = renderAnnotationAssignmentsAndDocComment(ext, env);
|
|
308
321
|
// Note: Not renderDefinitionReference, because we don't care if there
|
|
@@ -1236,10 +1249,14 @@ function csnToCdl( csn, options ) {
|
|
|
1236
1249
|
function renderTypeOrAnnotation( artifactName, art, env, artType ) {
|
|
1237
1250
|
let result = renderAnnotationAssignmentsAndDocComment(art, env);
|
|
1238
1251
|
result += `${env.indent + (artType || art.$syntax || art.kind )} ${renderArtifactName(artifactName, env)}`;
|
|
1239
|
-
if (art.includes)
|
|
1240
|
-
result += renderIncludes(art.includes, env);
|
|
1241
1252
|
|
|
1242
1253
|
const type = renderTypeReferenceAndProps(art, env);
|
|
1254
|
+
const isDirectStruct = type?.startsWith('{');
|
|
1255
|
+
if (art.includes?.length && isDirectStruct)
|
|
1256
|
+
// We can only render includes, if the type is directly structured. Otherwise, we would
|
|
1257
|
+
// render e.g. `type T : Include : T2;`, which is invalid. We use `extend` in such cases.
|
|
1258
|
+
result += renderIncludes(art.includes, env);
|
|
1259
|
+
|
|
1243
1260
|
if (type) {
|
|
1244
1261
|
// For nicer output, no colon if unnamed structure is used.
|
|
1245
1262
|
result += (!art.type && art.elements) ? ` ${type}` : ` : ${type}`;
|
|
@@ -1248,7 +1265,15 @@ function csnToCdl( csn, options ) {
|
|
|
1248
1265
|
msg.warning('syntax-missing-type', env.path, { name: artifactName },
|
|
1249
1266
|
'Missing type for definition $(NAME); can\'t be represented in CDL');
|
|
1250
1267
|
}
|
|
1268
|
+
|
|
1251
1269
|
result += ';\n';
|
|
1270
|
+
|
|
1271
|
+
if (art.includes?.length && !isDirectStruct) {
|
|
1272
|
+
// If we're not a directly structured type, render the `includes` as `extend`
|
|
1273
|
+
// statements directly below the type definition.
|
|
1274
|
+
result += renderExtendStatement(artifactName, { includes: art.includes }, env);
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1252
1277
|
return result;
|
|
1253
1278
|
}
|
|
1254
1279
|
|
|
@@ -1259,8 +1284,9 @@ function csnToCdl( csn, options ) {
|
|
|
1259
1284
|
*
|
|
1260
1285
|
* @param {CSN.Artifact} artifact
|
|
1261
1286
|
* @param {CdlRenderEnvironment} env
|
|
1262
|
-
* @param {object} [config={}]
|
|
1263
|
-
*
|
|
1287
|
+
* @param {object} [config={}]
|
|
1288
|
+
* @param {boolean} [config.typeRefOnly] Whether to only render type defs, no arrayed/structured/enum.
|
|
1289
|
+
* @param {boolean} [config.noAnnoCollect] Do not collect annotations of sub-elements.
|
|
1264
1290
|
* @return {string}
|
|
1265
1291
|
*/
|
|
1266
1292
|
function renderTypeReferenceAndProps( artifact, env, config = {} ) {
|
package/lib/render/toSql.js
CHANGED
|
@@ -459,7 +459,7 @@ function toSqlDdl( csn, options ) {
|
|
|
459
459
|
const eltStrOld = getEltStr(def.old, eltName, 'migration');
|
|
460
460
|
const eltStrNew = getEltStr(def.new, eltName, 'migration');
|
|
461
461
|
if (eltStrNew === eltStrOld)
|
|
462
|
-
|
|
462
|
+
continue; // Prevent spurious migrations, where the column DDL does not change.
|
|
463
463
|
|
|
464
464
|
const annosIncompat = [];
|
|
465
465
|
sqlSnippetAnnos
|
|
@@ -1379,15 +1379,11 @@ function toSqlDdl( csn, options ) {
|
|
|
1379
1379
|
* @returns {string} Rendered type
|
|
1380
1380
|
*/
|
|
1381
1381
|
function renderBuiltinType( typeName ) {
|
|
1382
|
-
const forHanaRenamesToEarly = {
|
|
1383
|
-
'cds.UTCDateTime': 'cds.DateTime',
|
|
1384
|
-
'cds.UTCTimestamp': 'cds.Timestamp',
|
|
1385
|
-
'cds.LocalDate': 'cds.Date',
|
|
1386
|
-
'cds.LocalTime': 'cds.Time',
|
|
1387
|
-
};
|
|
1388
|
-
const tName = forHanaRenamesToEarly[typeName] || typeName;
|
|
1389
1382
|
const types = cdsToSqlTypes[options.sqlDialect];
|
|
1390
|
-
|
|
1383
|
+
const result = types && types[typeName] || cdsToSqlTypes.standard[typeName];
|
|
1384
|
+
if (!result && options.testMode)
|
|
1385
|
+
throw new CompilerAssertion(`Expected to find a type mapping for ${typeName}`);
|
|
1386
|
+
return result || 'CHAR';
|
|
1391
1387
|
}
|
|
1392
1388
|
|
|
1393
1389
|
/**
|
|
@@ -258,16 +258,14 @@ const cdsToSqlTypes = {
|
|
|
258
258
|
'cds.DateTime': 'TIMESTAMP', // cds-compiler#2758
|
|
259
259
|
'cds.Timestamp': 'TIMESTAMP',
|
|
260
260
|
'cds.Boolean': 'BOOLEAN',
|
|
261
|
-
'cds.UUID': 'NVARCHAR', // changed to cds.String earlier
|
|
262
261
|
// (TODO: do it later; TODO: why not CHAR or at least VARCHAR?)
|
|
262
|
+
'cds.UUID': 'NVARCHAR', // changed to cds.String earlier
|
|
263
|
+
'cds.hana.ST_POINT': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
|
|
264
|
+
'cds.hana.ST_GEOMETRY': 'CHAR', // CHAR is implicit fallback used in toSql - make it explicit here
|
|
263
265
|
},
|
|
264
266
|
hana: {
|
|
265
267
|
'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
|
|
266
|
-
'cds.LocalDate': 'DATE',
|
|
267
|
-
'cds.LocalTime': 'TIME',
|
|
268
268
|
'cds.DateTime': 'SECONDDATE',
|
|
269
|
-
'cds.UTCDateTime': 'SECONDDATE',
|
|
270
|
-
'cds.UTCTimestamp': 'TIMESTAMP',
|
|
271
269
|
'cds.hana.ST_POINT': 'ST_POINT',
|
|
272
270
|
'cds.hana.ST_GEOMETRY': 'ST_GEOMETRY',
|
|
273
271
|
},
|
|
@@ -275,7 +273,7 @@ const cdsToSqlTypes = {
|
|
|
275
273
|
'cds.Date': 'DATE_TEXT',
|
|
276
274
|
'cds.Time': 'TIME_TEXT',
|
|
277
275
|
'cds.Timestamp': 'TIMESTAMP_TEXT',
|
|
278
|
-
'cds.DateTime': '
|
|
276
|
+
'cds.DateTime': 'DATETIME_TEXT',
|
|
279
277
|
'cds.Binary': 'BINARY_BLOB',
|
|
280
278
|
'cds.hana.BINARY': 'BINARY_BLOB',
|
|
281
279
|
'cds.hana.SMALLDECIMAL': 'SMALLDECIMAL',
|
|
@@ -310,6 +308,10 @@ const cdsToHdbcdsTypes = {
|
|
|
310
308
|
'cds.Int16': 'cds.hana.SMALLINT',
|
|
311
309
|
'cds.Int32': 'cds.Integer',
|
|
312
310
|
'cds.Int64': 'cds.Integer64',
|
|
311
|
+
'cds.Timestamp': 'cds.UTCTimestamp',
|
|
312
|
+
'cds.DateTime': 'cds.UTCDateTime',
|
|
313
|
+
'cds.Date': 'cds.LocalDate',
|
|
314
|
+
'cds.Time': 'cds.LocalTime',
|
|
313
315
|
};
|
|
314
316
|
|
|
315
317
|
/**
|
|
@@ -61,7 +61,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
61
61
|
* The customTransformers are applied here (and only here).
|
|
62
62
|
*
|
|
63
63
|
* @param {object | Array} _parent the thing that has _prop
|
|
64
|
-
* @param {string|number} _prop the name of the current property
|
|
64
|
+
* @param {string|number} _prop the name of the current property or index
|
|
65
65
|
* @param {object} node The value of node[_prop]
|
|
66
66
|
*/
|
|
67
67
|
function standard( _parent, _prop, node ) {
|
|
@@ -86,7 +86,7 @@ function getAssocToSkippedIgnorer( csn, options, messageFunctions, csnUtils ) {
|
|
|
86
86
|
!isPersistedOnDatabase(csn.definitions[member.target])) {
|
|
87
87
|
info(null, path,
|
|
88
88
|
{ target: member.target, anno: '@cds.persistence.skip' },
|
|
89
|
-
'Association has been removed as
|
|
89
|
+
'Association has been removed, as its target $(TARGET) is annotated with $(ANNO) and can\'t be rendered in SAP HANA SQL');
|
|
90
90
|
member.$ignore = true;
|
|
91
91
|
}
|
|
92
92
|
}
|