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