@sap/cds-compiler 4.9.6 → 5.1.0
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 +92 -0
- package/bin/cds_remove_invalid_whitespace.js +2 -1
- package/bin/cdsc.js +49 -19
- package/bin/cdshi.js +3 -1
- package/doc/CHANGELOG_BETA.md +7 -0
- package/lib/api/main.js +16 -19
- package/lib/api/options.js +5 -14
- package/lib/api/trace.js +0 -1
- package/lib/base/builtins.js +1 -0
- package/lib/base/location.js +4 -1
- package/lib/base/message-registry.js +43 -29
- package/lib/base/messages.js +23 -26
- package/lib/base/meta.js +10 -0
- package/lib/base/model.js +0 -2
- package/lib/base/node-helpers.js +0 -1
- package/lib/base/optionProcessorHelper.js +11 -0
- package/lib/checks/dbFeatureFlags.js +5 -0
- package/lib/checks/enricher.js +1 -5
- package/lib/checks/structuredAnnoExpressions.js +30 -0
- package/lib/checks/validator.js +8 -0
- package/lib/compiler/assert-consistency.js +5 -1
- package/lib/compiler/base.js +1 -1
- package/lib/compiler/builtins.js +18 -2
- package/lib/compiler/checks.js +2 -5
- package/lib/compiler/define.js +8 -8
- package/lib/compiler/extend.js +108 -37
- package/lib/compiler/generate.js +1 -1
- package/lib/compiler/index.js +27 -10
- package/lib/compiler/lsp-api.js +501 -2
- package/lib/compiler/populate.js +60 -13
- package/lib/compiler/propagator.js +10 -8
- package/lib/compiler/resolve.js +117 -94
- package/lib/compiler/shared.js +114 -32
- package/lib/compiler/tweak-assocs.js +31 -21
- package/lib/compiler/utils.js +2 -1
- package/lib/compiler/xsn-model.js +4 -0
- package/lib/edm/annotations/genericTranslation.js +69 -35
- package/lib/edm/csn2edm.js +16 -4
- package/lib/edm/edm.js +10 -3
- package/lib/edm/edmAnnoPreprocessor.js +1 -2
- package/lib/edm/edmPreprocessor.js +8 -10
- package/lib/gen/Dictionary.json +66 -2
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +2 -1
- package/lib/gen/languageParser.js +4995 -4817
- package/lib/json/csnVersion.js +1 -1
- package/lib/json/from-csn.js +4 -7
- package/lib/json/to-csn.js +25 -12
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/errorStrategy.js +0 -1
- package/lib/language/genericAntlrParser.js +35 -12
- package/lib/language/multiLineStringParser.js +3 -2
- package/lib/language/textUtils.js +1 -0
- package/lib/main.d.ts +28 -9
- package/lib/main.js +9 -9
- package/lib/model/cloneCsn.js +1 -0
- package/lib/model/csnRefs.js +22 -5
- package/lib/model/csnUtils.js +0 -14
- package/lib/model/revealInternalProperties.js +1 -2
- package/lib/modelCompare/compare.js +13 -11
- package/lib/optionProcessor.js +30 -9
- package/lib/render/manageConstraints.js +1 -1
- package/lib/render/toCdl.js +44 -14
- package/lib/render/toHdbcds.js +1 -2
- package/lib/render/toSql.js +45 -8
- package/lib/render/utils/common.js +12 -9
- package/lib/render/utils/stringEscapes.js +1 -0
- package/lib/transform/db/applyTransformations.js +13 -8
- package/lib/transform/db/associations.js +62 -54
- package/lib/transform/db/backlinks.js +20 -5
- package/lib/transform/db/expansion.js +1 -6
- package/lib/transform/db/flattening.js +86 -109
- package/lib/transform/db/killAnnotations.js +3 -0
- package/lib/transform/db/processSqlServices.js +63 -0
- package/lib/transform/db/temporal.js +3 -4
- package/lib/transform/db/views.js +0 -1
- package/lib/transform/draft/odata.js +56 -3
- package/lib/transform/effective/annotations.js +3 -2
- package/lib/transform/effective/flattening.js +135 -0
- package/lib/transform/effective/main.js +6 -4
- package/lib/transform/effective/types.js +13 -9
- package/lib/transform/forOdata.js +0 -2
- package/lib/transform/forRelationalDB.js +9 -19
- package/lib/transform/localized.js +7 -8
- package/lib/transform/odata/flattening.js +39 -31
- package/lib/transform/odata/typesExposure.js +5 -17
- package/lib/transform/transformUtils.js +1 -1
- package/lib/transform/translateAssocsToJoins.js +0 -1
- package/lib/utils/file.js +87 -8
- package/lib/utils/moduleResolve.js +59 -8
- package/lib/utils/term.js +3 -2
- package/package.json +7 -3
- package/share/messages/message-explanations.json +2 -0
- package/share/messages/type-unexpected-foreign-keys.md +52 -0
- package/share/messages/type-unexpected-on-condition.md +52 -0
package/lib/optionProcessor.js
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
// Compiler options
|
|
2
|
+
|
|
3
|
+
// Remarks:
|
|
4
|
+
// - The specification is client-tool centric (bin/cdsc.js):
|
|
5
|
+
// an option named `fooBar` is “produced” by `.option(' --foo-bar')`.
|
|
6
|
+
// - Also list the option in the `help` text, used with `cdsc -h`.
|
|
7
|
+
// - Specify valid values for non-boolean options in lib/api/validate.js.
|
|
8
|
+
|
|
1
9
|
'use strict';
|
|
2
10
|
|
|
3
11
|
const { createOptionProcessor } = require('./base/optionProcessorHelper');
|
|
@@ -15,6 +23,7 @@ optionProcessor
|
|
|
15
23
|
.option(' --options <file>')
|
|
16
24
|
.option('-w, --warning <level>', { valid: ['0', '1', '2', '3'] })
|
|
17
25
|
.option(' --quiet')
|
|
26
|
+
.option('-i, --stdin')
|
|
18
27
|
.option(' --show-message-id')
|
|
19
28
|
.option(' --no-message-id')
|
|
20
29
|
.option(' --no-message-context')
|
|
@@ -34,7 +43,7 @@ optionProcessor
|
|
|
34
43
|
.option(' --beta <list>')
|
|
35
44
|
.option(' --deprecated <list>')
|
|
36
45
|
.option(' --direct-backend')
|
|
37
|
-
.option(' --fallback-parser <type>', { valid: ['cdl', 'csn', 'csn!'] })
|
|
46
|
+
.option(' --fallback-parser <type>', { valid: [ 'auto!', 'cdl', 'csn', 'csn!' ] })
|
|
38
47
|
.option(' --shuffle <seed>') // 0 | 1..4294967296
|
|
39
48
|
.option(' --test-mode')
|
|
40
49
|
.option(' --test-sort-csn')
|
|
@@ -81,6 +90,7 @@ optionProcessor
|
|
|
81
90
|
--cds-home <dir> When set, modules starting with '@sap/cds/' are searched in <dir>
|
|
82
91
|
--module-lookup-directories <list> Comma separated list of directories to look
|
|
83
92
|
for CDS modules. Default is 'node_modules/'.
|
|
93
|
+
-i, --stdin Read input from stdin.
|
|
84
94
|
-- Indicate the end of options (helpful if source names start with "-")
|
|
85
95
|
|
|
86
96
|
Type options
|
|
@@ -117,9 +127,10 @@ optionProcessor
|
|
|
117
127
|
eagerPersistenceForGeneratedEntities
|
|
118
128
|
--fallback-parser <type> If the language cannot be deduced by the file's extensions, use this
|
|
119
129
|
parser as a fallback. Valid values are:
|
|
120
|
-
cdl
|
|
121
|
-
csn
|
|
122
|
-
csn!
|
|
130
|
+
cdl : Use CDL parser
|
|
131
|
+
csn : Use CSN parser
|
|
132
|
+
csn! : Use CSN parser even with extension cds, cdl, hdbcds and hdbdd
|
|
133
|
+
auto! : Ignore file extension; use CSN parser if file content starts with '{'
|
|
123
134
|
--direct-backend Do not compile the given CSN but directly pass it to the backend.
|
|
124
135
|
Can only be used with certain new CSN based backends. Combination with
|
|
125
136
|
other flags is limited, e.g. --test-mode will not run a consistency check.
|
|
@@ -161,6 +172,8 @@ optionProcessor
|
|
|
161
172
|
Environment variables
|
|
162
173
|
NO_COLOR If set, compiler messages (/output) will not be colored.
|
|
163
174
|
Can be overwritten by '--color'
|
|
175
|
+
FORCE_COLOR If set, compiler messages (/output) will be colored. Overrides NO_COLOR.
|
|
176
|
+
Can be overwritten by '--color'
|
|
164
177
|
CDSC_TRACE_TIME If set, additional timing information is printed to stderr.
|
|
165
178
|
CDSC_TRACE_API If set, additional API calling information is printed to stderr.
|
|
166
179
|
`);
|
|
@@ -318,9 +331,9 @@ optionProcessor.command('Q, toSql')
|
|
|
318
331
|
.option(' --pre2134ReferentialConstraintNames')
|
|
319
332
|
.option(' --disable-hana-comments')
|
|
320
333
|
.option(' --generated-by-comment')
|
|
321
|
-
.option(' --better-sqlite-session-variables')
|
|
334
|
+
.option(' --better-sqlite-session-variables <bool>')
|
|
322
335
|
.option(' --fewer-localized-views')
|
|
323
|
-
.option(' --
|
|
336
|
+
.option(' --with-hana-associations <bool>', { valid: [ 'true', 'false' ] })
|
|
324
337
|
.help(`
|
|
325
338
|
Usage: cdsc toSql [options] <files...>
|
|
326
339
|
|
|
@@ -372,11 +385,17 @@ optionProcessor.command('Q, toSql')
|
|
|
372
385
|
--pre2134ReferentialConstraintNames Do not prefix the constraint identifier with "c__"
|
|
373
386
|
--disable-hana-comments Disable rendering of doc comments as SAP HANA comments.
|
|
374
387
|
--generated-by-comment Enable rendering of the initial SQL comment for HDI-based artifacts
|
|
375
|
-
--better-sqlite-session-variables
|
|
376
|
-
|
|
388
|
+
--better-sqlite-session-variables <bool>
|
|
389
|
+
Enable better-sqlite compatible rendering of $user. Only
|
|
390
|
+
active if sqlDialect is \`sqlite\`:
|
|
391
|
+
true : (default) Render better-sqlite session_context(…)
|
|
392
|
+
false : Render session variables as string literals, used e.g. with sqlite3 driver
|
|
377
393
|
--fewer-localized-views If set, the backends will not create localized convenience views for
|
|
378
394
|
those views, that only have an association to a localized entity/view.
|
|
379
|
-
--
|
|
395
|
+
--with-hana-associations <bool>
|
|
396
|
+
Enable and disable rendering of "WITH ASSOCIATIONS" for sqlDialect 'hana'.
|
|
397
|
+
true : (default) Render "WITH ASSOCIATIONS"
|
|
398
|
+
false : Do not render "WITH ASSOCIATIONS"
|
|
380
399
|
`);
|
|
381
400
|
|
|
382
401
|
optionProcessor.command('toRename')
|
|
@@ -482,6 +501,7 @@ optionProcessor.command('toCsn')
|
|
|
482
501
|
optionProcessor.command('parseCdl')
|
|
483
502
|
.option('-h, --help')
|
|
484
503
|
.positionalArgument('<file>')
|
|
504
|
+
.option(' --with-locations')
|
|
485
505
|
.help(`
|
|
486
506
|
Usage: cdsc parseCdl [options] <file>
|
|
487
507
|
|
|
@@ -489,6 +509,7 @@ optionProcessor.command('parseCdl')
|
|
|
489
509
|
resolve imports, apply extensions or expand any queries.
|
|
490
510
|
|
|
491
511
|
Options
|
|
512
|
+
--with-locations Add $location to CSN artifacts.
|
|
492
513
|
-h, --help Show this help text
|
|
493
514
|
`);
|
|
494
515
|
|
|
@@ -58,7 +58,7 @@ function alterConstraintsWithCsn( csn, options, messageFunctions ) {
|
|
|
58
58
|
// TODO: Remove / Move to api/options.js once alterConstraintsWithCsn is available outside bin/cdsc
|
|
59
59
|
function _transformSqlOptions( csn, options ) {
|
|
60
60
|
const { src } = options;
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
const prepareOptions = require('../api/options');
|
|
63
63
|
options = prepareOptions.to.sql(options);
|
|
64
64
|
options.src = src;
|
package/lib/render/toCdl.js
CHANGED
|
@@ -13,11 +13,11 @@ const { typeParameters, specialFunctions } = require('../compiler/builtins');
|
|
|
13
13
|
const { isAnnotationExpression } = require('../base/builtins');
|
|
14
14
|
const { forEach } = require('../utils/objectUtils');
|
|
15
15
|
const {
|
|
16
|
-
generatedByCompilerVersion,
|
|
17
16
|
getNormalizedQuery,
|
|
18
17
|
} = require('../model/csnUtils');
|
|
19
18
|
const { isBuiltinType } = require('../base/builtins');
|
|
20
19
|
const { cloneFullCsn } = require('../model/cloneCsn');
|
|
20
|
+
const { getKeysDict } = require('../model/csnRefs');
|
|
21
21
|
|
|
22
22
|
const identifierRegex = /^[$_a-zA-Z][$_a-zA-Z0-9]*$/;
|
|
23
23
|
const specialFunctionKeywords = Object.create(null);
|
|
@@ -62,7 +62,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
62
62
|
const hanaRequiresAbsolutePath = usings.available.includes('hana');
|
|
63
63
|
|
|
64
64
|
const cdlResult = Object.create(null);
|
|
65
|
-
cdlResult.model =
|
|
65
|
+
cdlResult.model = '';
|
|
66
66
|
|
|
67
67
|
const subelementAnnotates = [];
|
|
68
68
|
|
|
@@ -676,7 +676,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
676
676
|
artifact = artifact.items;
|
|
677
677
|
}
|
|
678
678
|
|
|
679
|
-
if (!artifact.elements && !artifact.enum)
|
|
679
|
+
if (!artifact.elements && !artifact.enum && !artifact.keys)
|
|
680
680
|
return null;
|
|
681
681
|
|
|
682
682
|
const annotate = { annotate: env.path[1] };
|
|
@@ -712,15 +712,16 @@ function csnToCdl( csn, options, msg ) {
|
|
|
712
712
|
*/
|
|
713
713
|
function collectAnnos( annotateObj, art ) {
|
|
714
714
|
if (!Object.hasOwnProperty.call(art, 'elements') &&
|
|
715
|
-
|
|
715
|
+
!Object.hasOwnProperty.call(art, 'enum') &&
|
|
716
|
+
!Object.hasOwnProperty.call(art, 'keys'))
|
|
716
717
|
return false;
|
|
717
718
|
|
|
718
|
-
const
|
|
719
|
-
// Use "elements" for
|
|
719
|
+
const dict = art.enum || art.keys && getKeysDict(art) || art.elements;
|
|
720
|
+
// Use "elements" for all. This is allowed in extensions.
|
|
720
721
|
const collected = { elements: Object.create(null) };
|
|
721
722
|
let hasAnnotation = false;
|
|
722
723
|
|
|
723
|
-
forEach(
|
|
724
|
+
forEach(dict, (elemName, element) => {
|
|
724
725
|
if (!collected.elements[elemName])
|
|
725
726
|
collected.elements[elemName] = { };
|
|
726
727
|
|
|
@@ -1379,7 +1380,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1379
1380
|
|
|
1380
1381
|
// Foreign keys (if any, unless we also have an ON_condition (which means we have been transformed from managed to unmanaged)
|
|
1381
1382
|
if (artifact.keys && !artifact.on)
|
|
1382
|
-
result += `
|
|
1383
|
+
result += ` ${ renderForeignKeys(artifact, env) }`;
|
|
1383
1384
|
|
|
1384
1385
|
if (artifact.notNull !== undefined && !artifact.on) // unmanaged associations can't be followed by "not null"
|
|
1385
1386
|
result += renderNullability(artifact);
|
|
@@ -1435,7 +1436,7 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1435
1436
|
if (art.on)
|
|
1436
1437
|
result += ` on ${exprRenderer.renderExpr(art.on, env.withSubPath([ 'on' ]))}`;
|
|
1437
1438
|
else if (art.keys)
|
|
1438
|
-
result += `
|
|
1439
|
+
result += ` ${ renderForeignKeys(art, env) }`;
|
|
1439
1440
|
return result;
|
|
1440
1441
|
}
|
|
1441
1442
|
|
|
@@ -1813,15 +1814,43 @@ function csnToCdl( csn, options, msg ) {
|
|
|
1813
1814
|
}
|
|
1814
1815
|
|
|
1815
1816
|
/**
|
|
1816
|
-
* Render
|
|
1817
|
+
* Render foreign keys.
|
|
1817
1818
|
*
|
|
1818
|
-
* @param {object}
|
|
1819
|
+
* @param {object} art
|
|
1819
1820
|
* @param {CdlRenderEnvironment} env
|
|
1820
1821
|
* @return {string}
|
|
1821
1822
|
*/
|
|
1822
|
-
function
|
|
1823
|
-
const
|
|
1824
|
-
|
|
1823
|
+
function renderForeignKeys( art, env ) {
|
|
1824
|
+
const renderedKeys = [];
|
|
1825
|
+
let hasAnnotations = false;
|
|
1826
|
+
env = env.withSubPath([ 'keys', -1 ]);
|
|
1827
|
+
env.increaseIndent();
|
|
1828
|
+
|
|
1829
|
+
for (let i = 0; i < art.keys.length; ++i) {
|
|
1830
|
+
env.path[env.path.length - 1] = i;
|
|
1831
|
+
const fKey = art.keys[i];
|
|
1832
|
+
|
|
1833
|
+
const annos = renderAnnotationAssignmentsAndDocComment(fKey, env).trim();
|
|
1834
|
+
if (annos) {
|
|
1835
|
+
hasAnnotations = true;
|
|
1836
|
+
renderedKeys.push(annos);
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
const alias = fKey.as ? renderAlias(fKey.as, env) : '';
|
|
1840
|
+
const key = exprRenderer.renderExpr(fKey, env);
|
|
1841
|
+
renderedKeys.push(`${key}${alias},`);
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
if (hasAnnotations) {
|
|
1845
|
+
const sep = `\n${env.indent}`;
|
|
1846
|
+
env.decreaseIndent();
|
|
1847
|
+
return `{${sep}${renderedKeys.join(sep)}\n${env.indent}}`;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
let result = renderedKeys.join(' ');
|
|
1851
|
+
if (result[result.length - 1] === ',') // remove trailing comma
|
|
1852
|
+
result = result.slice(0, -1);
|
|
1853
|
+
return `{ ${ result } }`;
|
|
1825
1854
|
}
|
|
1826
1855
|
|
|
1827
1856
|
/**
|
|
@@ -2427,6 +2456,7 @@ function isSimpleString( str ) {
|
|
|
2427
2456
|
// <https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf>
|
|
2428
2457
|
// On top, because (invalid) surrogate pairs need to be handled, we check for them as well.
|
|
2429
2458
|
// v3: Not a simple string if ' (\u0027) is in string.
|
|
2459
|
+
// eslint-disable-next-line no-control-regex
|
|
2430
2460
|
return str === '' || (/^[^\u{0000}-\u{001F}\u2028\u2029]+$/u.test(str) &&
|
|
2431
2461
|
!hasUnpairedUnicodeSurrogate(str));
|
|
2432
2462
|
}
|
package/lib/render/toHdbcds.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
4
|
getLastPartOf, getLastPartOfRef,
|
|
5
|
-
hasValidSkipOrExists,
|
|
5
|
+
hasValidSkipOrExists, getNormalizedQuery,
|
|
6
6
|
getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement, hasAnnotationValue,
|
|
7
7
|
pathName,
|
|
8
8
|
} = require('../model/csnUtils');
|
|
@@ -167,7 +167,6 @@ function toHdbcdsSource( csn, options, messageFunctions ) {
|
|
|
167
167
|
if (sourceStr !== '') {
|
|
168
168
|
const name = plainNames ? artifactName.replace(/\./g, '_').toUpperCase() : artifactName;
|
|
169
169
|
hdbcds[name] = [
|
|
170
|
-
!options.testMode ? `// ${generatedByCompilerVersion()} \n` : '',
|
|
171
170
|
renderNamespaceDeclaration(name, env),
|
|
172
171
|
renderUsings(name, env),
|
|
173
172
|
sourceStr,
|
package/lib/render/toSql.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
const {
|
|
5
5
|
getLastPartOf, getLastPartOfRef,
|
|
6
|
-
hasValidSkipOrExists,
|
|
6
|
+
hasValidSkipOrExists, getNormalizedQuery,
|
|
7
7
|
forEachDefinition, getResultingName,
|
|
8
8
|
getVariableReplacement, pathName,
|
|
9
9
|
} = require('../model/csnUtils');
|
|
@@ -186,8 +186,11 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
186
186
|
deletions: Object.create(null),
|
|
187
187
|
constraintDeletions: [],
|
|
188
188
|
migrations: Object.create(null),
|
|
189
|
+
hdbrole: Object.create(null),
|
|
189
190
|
};
|
|
190
191
|
|
|
192
|
+
const sqlServiceEntities = Object.create(null);
|
|
193
|
+
|
|
191
194
|
// Registries for artifact and element names per CSN section
|
|
192
195
|
const definitionsDuplicateChecker = new DuplicateChecker(options.sqlMapping);
|
|
193
196
|
const deletionsDuplicateChecker = new DuplicateChecker();
|
|
@@ -252,6 +255,25 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
252
255
|
});
|
|
253
256
|
}
|
|
254
257
|
|
|
258
|
+
// Can only happen for HDI based deployment
|
|
259
|
+
// .hdbrole documentation: https://help.sap.com/docs/SAP_HANA_PLATFORM/3823b0f33420468ba5f1cf7f59bd6bd9/625d7733c30b4666b4a522d7fa68a550.html
|
|
260
|
+
Object.keys(sqlServiceEntities).forEach((sqlServiceName) => {
|
|
261
|
+
const accessRole = {
|
|
262
|
+
role: {
|
|
263
|
+
name: renderArtifactNameWithoutQuotes(`${sqlServiceName }.access`),
|
|
264
|
+
object_privileges: Object.entries(sqlServiceEntities[sqlServiceName]).map(([ name, entity ]) => ({
|
|
265
|
+
name: renderArtifactNameWithoutQuotes(name),
|
|
266
|
+
type: entity.query || entity.projection ? 'VIEW' : 'TABLE',
|
|
267
|
+
privileges: [ 'SELECT' ],
|
|
268
|
+
privileges_with_grant_option: [],
|
|
269
|
+
})),
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
if (accessRole.role.object_privileges.length > 0)
|
|
274
|
+
mainResultObj.hdbrole[`${sqlServiceName }_access`] = JSON.stringify(accessRole, null, 2);
|
|
275
|
+
});
|
|
276
|
+
|
|
255
277
|
// trigger artifact and element name checks
|
|
256
278
|
definitionsDuplicateChecker.check(error, options);
|
|
257
279
|
extensionsDuplicateChecker.check(error);
|
|
@@ -264,7 +286,6 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
264
286
|
// (relying on the order of dictionaries above)
|
|
265
287
|
// FIXME: Should consider inter-view dependencies, too
|
|
266
288
|
const sql = Object.create(null);
|
|
267
|
-
const sqlVersionLine = `-- ${generatedByCompilerVersion()}\n`;
|
|
268
289
|
|
|
269
290
|
// Handle hdbKinds separately from alterTable case
|
|
270
291
|
const {
|
|
@@ -278,10 +299,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
278
299
|
// Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
|
|
279
300
|
if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
|
|
280
301
|
sourceString = sourceString.slice('COLUMN '.length);
|
|
281
|
-
sql[name] =
|
|
282
|
-
}
|
|
283
|
-
else if (!options.testMode && options.generatedByComment) {
|
|
284
|
-
mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
|
|
302
|
+
sql[name] = `CREATE ${sourceString};`;
|
|
285
303
|
}
|
|
286
304
|
}
|
|
287
305
|
if (options.src === 'sql')
|
|
@@ -295,7 +313,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
295
313
|
|
|
296
314
|
forEachKey(alterStmts, (constraintName) => {
|
|
297
315
|
if (!csn.unchangedConstraints?.has(constraintName))
|
|
298
|
-
constraints[constraintName] = `${
|
|
316
|
+
constraints[constraintName] = `${alterStmts[constraintName]}`;
|
|
299
317
|
});
|
|
300
318
|
mainResultObj.constraints = constraints;
|
|
301
319
|
}
|
|
@@ -304,7 +322,7 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
304
322
|
mainResultObj.sql = sql;
|
|
305
323
|
|
|
306
324
|
for (const name in deletions)
|
|
307
|
-
deletions[name] = `${
|
|
325
|
+
deletions[name] = `${deletions[name]}`;
|
|
308
326
|
|
|
309
327
|
timetrace.stop('SQL rendering');
|
|
310
328
|
return mainResultObj;
|
|
@@ -325,6 +343,10 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
325
343
|
|
|
326
344
|
switch (art.kind) {
|
|
327
345
|
case 'entity':
|
|
346
|
+
if (art.$sqlService) { // collect entities that are in a sql service so we can render the .hdbrole later
|
|
347
|
+
sqlServiceEntities[art.$sqlService] ??= Object.create(null);
|
|
348
|
+
sqlServiceEntities[art.$sqlService][artifactName] = art;
|
|
349
|
+
}
|
|
328
350
|
if (art.query || art.projection) {
|
|
329
351
|
const result = renderView(artifactName, art, env);
|
|
330
352
|
if (result)
|
|
@@ -350,6 +372,20 @@ function toSqlDdl( csn, options, messageFunctions ) {
|
|
|
350
372
|
}
|
|
351
373
|
}
|
|
352
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Render the given artifactName according to the sqlMapping, but
|
|
377
|
+
* - uppercased for plain
|
|
378
|
+
* - without enclosing " for quoted/hdbcds
|
|
379
|
+
*
|
|
380
|
+
* @param {string} artifactName
|
|
381
|
+
* @returns {string}
|
|
382
|
+
*/
|
|
383
|
+
function renderArtifactNameWithoutQuotes( artifactName ) {
|
|
384
|
+
if (options.sqlMapping === 'plain')
|
|
385
|
+
return renderArtifactName(artifactName).toUpperCase();
|
|
386
|
+
return renderArtifactName(artifactName).slice(1, -1); // trim leading/trailing "
|
|
387
|
+
}
|
|
388
|
+
|
|
353
389
|
/**
|
|
354
390
|
* Render an artifact extension into the appropriate dictionary of 'resultObj'.
|
|
355
391
|
* Only SAP HANA SQL is currently supported.
|
|
@@ -1645,6 +1681,7 @@ function renderStringForSql( str, sqlDialect ) {
|
|
|
1645
1681
|
// > Single quotation marks are used to delimit string literals.
|
|
1646
1682
|
// > A single quotation mark itself can be represented using two single quotation marks.
|
|
1647
1683
|
str = str.replace(/'/g, '\'\'')
|
|
1684
|
+
// eslint-disable-next-line no-control-regex
|
|
1648
1685
|
.replace(/\u{0}/ug, '\' || CHAR(0) || \'');
|
|
1649
1686
|
}
|
|
1650
1687
|
else {
|
|
@@ -372,7 +372,7 @@ const variablesToSql = {
|
|
|
372
372
|
'$valid.to': "current_setting('cap.valid_to')::timestamp",
|
|
373
373
|
$now: 'current_timestamp',
|
|
374
374
|
},
|
|
375
|
-
|
|
375
|
+
sqlite: {
|
|
376
376
|
'$user.id': "session_context( '$user.id' )",
|
|
377
377
|
'$user.locale': "session_context( '$user.locale' )",
|
|
378
378
|
$tenant: "session_context( '$tenant' )",
|
|
@@ -382,7 +382,7 @@ const variablesToSql = {
|
|
|
382
382
|
'$valid.to': "session_context( '$valid.to' )",
|
|
383
383
|
$now: 'CURRENT_TIMESTAMP',
|
|
384
384
|
},
|
|
385
|
-
sqlite: {
|
|
385
|
+
'old-sqlite': {
|
|
386
386
|
// For sqlite, we render the string-format-time (strftime) function.
|
|
387
387
|
// Because the format of `current_timestamp` is like that: '2021-05-14 09:17:19' whereas
|
|
388
388
|
// the format for timestamps (at least in Node.js) is like that: '2021-01-01T00:00:00.000Z'
|
|
@@ -394,11 +394,14 @@ const variablesToSql = {
|
|
|
394
394
|
'$valid.to': "strftime('%Y-%m-%dT%H:%M:%S.001Z', 'now')",
|
|
395
395
|
$now: 'CURRENT_TIMESTAMP',
|
|
396
396
|
},
|
|
397
|
-
plain: {
|
|
398
|
-
'$
|
|
399
|
-
'$
|
|
400
|
-
|
|
401
|
-
'$
|
|
397
|
+
plain: { // better-sqlite defaults
|
|
398
|
+
'$user.id': "session_context( '$user.id' )",
|
|
399
|
+
'$user.locale': "session_context( '$user.locale' )",
|
|
400
|
+
$tenant: "session_context( '$tenant' )",
|
|
401
|
+
'$at.from': "session_context( '$valid.from' )",
|
|
402
|
+
'$at.to': "session_context( '$valid.to' )",
|
|
403
|
+
'$valid.from': "session_context( '$valid.from' )",
|
|
404
|
+
'$valid.to': "session_context( '$valid.to' )",
|
|
402
405
|
$now: 'CURRENT_TIMESTAMP',
|
|
403
406
|
},
|
|
404
407
|
h2: {
|
|
@@ -423,8 +426,8 @@ const variablesToSql = {
|
|
|
423
426
|
* @return {string|null} `null` if the variable could not be found for the given dialect and in the fallback values.
|
|
424
427
|
*/
|
|
425
428
|
function variableForDialect( options, variable ) {
|
|
426
|
-
const dialect = options.sqlDialect === 'sqlite' && options.betterSqliteSessionVariables
|
|
427
|
-
? '
|
|
429
|
+
const dialect = options.sqlDialect === 'sqlite' && options.betterSqliteSessionVariables === false
|
|
430
|
+
? 'old-sqlite'
|
|
428
431
|
: options.sqlDialect;
|
|
429
432
|
return variablesToSql[dialect]?.[variable] || variablesToSql.fallback[variable] || null;
|
|
430
433
|
}
|
|
@@ -45,6 +45,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
const csnPath = [ ...path ];
|
|
48
|
+
const context = {};
|
|
48
49
|
if (prop === 'definitions') {
|
|
49
50
|
definitions( parent, 'definitions', parent.definitions );
|
|
50
51
|
}
|
|
@@ -84,9 +85,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
84
85
|
for (const name of Object.getOwnPropertyNames( node )) {
|
|
85
86
|
const trans = transformers[name] || transformers[name.charAt(0)] || standard;
|
|
86
87
|
if (customTransformers[name])
|
|
87
|
-
customTransformers[name](node, name, node[name], csnPath, _parent, _prop);
|
|
88
|
+
customTransformers[name](node, name, node[name], csnPath, _parent, _prop, context);
|
|
88
89
|
else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
|
|
89
|
-
customTransformers['@'](node, name, node[name], csnPath, _parent, _prop);
|
|
90
|
+
customTransformers['@'](node, name, node[name], csnPath, _parent, _prop, context);
|
|
90
91
|
trans( node, name, node[name], csnPath );
|
|
91
92
|
}
|
|
92
93
|
}
|
|
@@ -110,9 +111,9 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
110
111
|
for (const name of Object.getOwnPropertyNames( node )) {
|
|
111
112
|
const trans = transformers[name] || transformers[name.charAt(0)] || standard;
|
|
112
113
|
if (customTransformers[name])
|
|
113
|
-
customTransformers[name](node, name, node[name], csnPath, dict);
|
|
114
|
+
customTransformers[name](node, name, node[name], csnPath, dict, null, context);
|
|
114
115
|
else if (options.processAnnotations && customTransformers['@'] && name.charAt(0) === '@')
|
|
115
|
-
customTransformers['@'](node, name, node[name], csnPath, dict);
|
|
116
|
+
customTransformers['@'](node, name, node[name], csnPath, dict, null, context);
|
|
116
117
|
trans( node, name, node[name], csnPath );
|
|
117
118
|
}
|
|
118
119
|
csnPath.pop();
|
|
@@ -130,12 +131,14 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
130
131
|
if (options.skipDict?.[_prop] || dict == null) // with universal CSN, dictionaries might be null
|
|
131
132
|
return;
|
|
132
133
|
csnPath.push( _prop );
|
|
134
|
+
context[`$in_${_prop}`] = true;
|
|
133
135
|
for (const name of Object.getOwnPropertyNames( dict ))
|
|
134
136
|
dictEntry( dict, name, dict[name] );
|
|
135
137
|
|
|
136
138
|
if (!Object.prototype.propertyIsEnumerable.call( node, _prop ))
|
|
137
139
|
setProp(node, `$${_prop}`, dict);
|
|
138
140
|
csnPath.pop();
|
|
141
|
+
context[`$in_${_prop}`] = undefined;
|
|
139
142
|
}
|
|
140
143
|
|
|
141
144
|
/**
|
|
@@ -148,6 +151,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
148
151
|
*/
|
|
149
152
|
function annotation( _parent, _prop, node ) {
|
|
150
153
|
if (options.processAnnotations) {
|
|
154
|
+
context.$annotation = { name: _prop, value: node };
|
|
151
155
|
if (isAnnotationExpression(node)) {
|
|
152
156
|
standard(_parent, _prop, node);
|
|
153
157
|
}
|
|
@@ -164,6 +168,7 @@ function applyTransformationsInternal( parent, prop, customTransformers, artifac
|
|
|
164
168
|
|
|
165
169
|
csnPath.pop();
|
|
166
170
|
}
|
|
171
|
+
context.$annotation = undefined;
|
|
167
172
|
}
|
|
168
173
|
}
|
|
169
174
|
|
|
@@ -359,18 +364,18 @@ function mergeTransformers( transformers, that ) {
|
|
|
359
364
|
remapped[n] = [];
|
|
360
365
|
|
|
361
366
|
if (Array.isArray(fns)) {
|
|
362
|
-
remapped[n].push((parent, name, prop, path, parentParent) => fns.forEach(
|
|
363
|
-
fn => fn.bind(that)(parent, name, prop, path, parentParent)
|
|
367
|
+
remapped[n].push((parent, name, prop, path, parentParent, opt, context) => fns.forEach(
|
|
368
|
+
fn => fn.bind(that)(parent, name, prop, path, parentParent, opt, context)
|
|
364
369
|
));
|
|
365
370
|
}
|
|
366
371
|
else {
|
|
367
|
-
remapped[n].push((parent, name, prop, path, parentParent) => fns.bind(that)(parent, name, prop, path, parentParent));
|
|
372
|
+
remapped[n].push((parent, name, prop, path, parentParent, opt, context) => fns.bind(that)(parent, name, prop, path, parentParent, opt, context));
|
|
368
373
|
}
|
|
369
374
|
}
|
|
370
375
|
}
|
|
371
376
|
|
|
372
377
|
for (const [ n, fns ] of Object.entries(remapped))
|
|
373
|
-
remapped[n] = (parent, name, prop, path, parentParent) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent));
|
|
378
|
+
remapped[n] = (parent, name, prop, path, parentParent, opt, context) => fns.forEach(fn => fn.bind(that)(parent, name, prop, path, parentParent, opt, context));
|
|
374
379
|
|
|
375
380
|
|
|
376
381
|
return remapped;
|
|
@@ -132,44 +132,8 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
132
132
|
* @param {string} artifactName Name of the artifact
|
|
133
133
|
*/
|
|
134
134
|
function handleManagedAssocSteps( artifact, artifactName ) {
|
|
135
|
-
const transformer =
|
|
136
|
-
|
|
137
|
-
// [<assoc base>.]<managed assoc>.<field>
|
|
138
|
-
if (ref.length > 1) {
|
|
139
|
-
const { links } = inspectRef(path);
|
|
140
|
-
if (links) {
|
|
141
|
-
let fkAlias = '';
|
|
142
|
-
// eslint-disable-next-line for-direction
|
|
143
|
-
for (let i = links.length - 1; i >= 0; i--) {
|
|
144
|
-
const link = links[i];
|
|
145
|
-
// We found the latest managed assoc path step
|
|
146
|
-
if (link.art && link.art.target && link.art.keys &&
|
|
147
|
-
// Doesn't work when ref-target (filter condition) or similar is used
|
|
148
|
-
!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
|
|
149
|
-
const fkRef = ref[i + 1];
|
|
150
|
-
const fkName = (!fkAlias ? fkRef : `${fkRef}${pathDelimiter}${fkAlias}`);
|
|
151
|
-
const fks = link.art.keys.filter(key => key.ref[0] === fkName);
|
|
152
|
-
|
|
153
|
-
if (fks.length >= 1) { // after flattening, at most one FK will remain.
|
|
154
|
-
// `.as` is set for SQL, but not for OData -> fall back to implicit alias
|
|
155
|
-
fkAlias = fks[0].as || fks[0].ref[fks[0].ref.length - 1];
|
|
156
|
-
const source = findSource(links, i - 1) || artifact;
|
|
157
|
-
const managedAssocStepName = ref[i];
|
|
158
|
-
const newFkName = `${managedAssocStepName}${pathDelimiter}${fkAlias}`;
|
|
159
|
-
if (source?.elements[newFkName])
|
|
160
|
-
refOwner.ref = [ ...ref.slice(0, i), newFkName ];
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
fkAlias = '';
|
|
165
|
-
// Ignore last path step and unmanaged associations.
|
|
166
|
-
// Structures should have been already flattened.
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
},
|
|
172
|
-
};
|
|
135
|
+
const transformer = getTransformer();
|
|
136
|
+
const inColumnsTransformer = getTransformer(true);
|
|
173
137
|
for (const elemName in artifact.elements) {
|
|
174
138
|
const elem = artifact.elements[elemName];
|
|
175
139
|
// The association is an unmanaged one
|
|
@@ -187,28 +151,72 @@ function getFKAccessFinalizer( csn, csnUtils, pathDelimiter, processOnInQueries
|
|
|
187
151
|
where: transform,
|
|
188
152
|
having: transform,
|
|
189
153
|
};
|
|
190
|
-
if (processOnInQueries)
|
|
154
|
+
if (processOnInQueries) {
|
|
155
|
+
queryTransformers.columns = (parent, prop, columns, path) => {
|
|
156
|
+
for (let i = 0; i < columns.length; i++) {
|
|
157
|
+
const column = columns[i];
|
|
158
|
+
if (column.ref) {
|
|
159
|
+
inColumnsTransformer.ref(column, 'ref', column.ref, path.concat([ 'columns', i ]));
|
|
160
|
+
column.ref.forEach((step, index) => {
|
|
161
|
+
if (step.where)
|
|
162
|
+
transform(step, 'where', step.where, path.concat([ 'columns', i, 'ref', index ]));
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
};
|
|
191
167
|
queryTransformers.on = transform;
|
|
192
|
-
|
|
168
|
+
}
|
|
169
|
+
applyTransformationsOnNonDictionary(artifact, artifact.query ? 'query' : 'projection', queryTransformers, { drillRef: true }, [ 'definitions', artifactName ]);
|
|
193
170
|
}
|
|
194
171
|
|
|
195
|
-
|
|
196
172
|
/**
|
|
197
|
-
* Find out where the managed association is
|
|
198
173
|
*
|
|
199
|
-
* @param {
|
|
200
|
-
* @
|
|
201
|
-
* @returns {object | undefined} CSN definition of the source of the managed association
|
|
174
|
+
* @param {boolean} isColumns Whether the transformation is taking place on a column
|
|
175
|
+
* @returns {object}
|
|
202
176
|
*/
|
|
203
|
-
function
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
177
|
+
function getTransformer( isColumns = false ) {
|
|
178
|
+
return {
|
|
179
|
+
ref: (refOwner, prop, ref, path) => {
|
|
180
|
+
// [<assoc base>.]<managed assoc>.<field>
|
|
181
|
+
if (ref.length > 1) {
|
|
182
|
+
const { links } = inspectRef(path);
|
|
183
|
+
if (links) {
|
|
184
|
+
let fkAlias = '';
|
|
185
|
+
for (let i = links.length - 1; i >= 0; i--) {
|
|
186
|
+
const link = links[i];
|
|
187
|
+
// We found the latest managed assoc path step
|
|
188
|
+
if (link.art && link.art.target && link.art.keys &&
|
|
189
|
+
// Doesn't work when ref-target (filter condition) or similar is used
|
|
190
|
+
!ref.slice(i).some(refElement => typeof refElement !== 'string')) {
|
|
191
|
+
const fkRef = ref[i + 1];
|
|
192
|
+
const fkName = (!fkAlias ? fkRef : `${fkRef}${pathDelimiter}${fkAlias}`);
|
|
193
|
+
const fks = link.art.keys.filter(key => key.ref[0] === fkName);
|
|
194
|
+
|
|
195
|
+
if (fks.length >= 1) { // after flattening, at most one FK will remain.
|
|
196
|
+
// `.as` is set for SQL, but not for OData -> fall back to implicit alias
|
|
197
|
+
fkAlias = fks[0].as || fks[0].ref[fks[0].ref.length - 1];
|
|
198
|
+
const managedAssocStepName = ref[i];
|
|
199
|
+
const newFkName = `${managedAssocStepName}${pathDelimiter}${fkAlias}`;
|
|
200
|
+
if (isColumns) {
|
|
201
|
+
refOwner.ref = [ ...ref.slice(0, i), newFkName ];
|
|
202
|
+
if (!refOwner.as)
|
|
203
|
+
refOwner.as = implicitAs(ref);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
refOwner.ref = [ ...ref.slice(0, i), newFkName ];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
fkAlias = '';
|
|
212
|
+
// Ignore last path step and unmanaged associations.
|
|
213
|
+
// Structures should have been already flattened.
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
};
|
|
212
220
|
}
|
|
213
221
|
}
|
|
214
222
|
}
|