@sap/cds-compiler 2.11.2 → 2.13.6
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 +175 -2
- package/bin/.eslintrc.json +1 -2
- package/bin/cds_update_identifiers.js +10 -8
- package/bin/cdsc.js +23 -17
- package/bin/cdsse.js +2 -2
- package/bin/cdsv2m.js +3 -2
- package/doc/CHANGELOG_ARCHIVE.md +1 -1
- package/doc/CHANGELOG_BETA.md +25 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +32 -79
- package/lib/api/options.js +3 -2
- package/lib/api/validate.js +2 -1
- package/lib/backends.js +16 -26
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +10 -19
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +75 -9
- package/lib/base/messages.js +31 -35
- package/lib/base/model.js +2 -62
- package/lib/base/optionProcessorHelper.js +246 -183
- package/lib/checks/.eslintrc.json +2 -0
- package/lib/checks/actionsFunctions.js +2 -1
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/emptyOrOnlyVirtual.js +2 -2
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/foreignKeys.js +4 -4
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedInType.js +4 -4
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/queryNoDbArtifacts.js +1 -3
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +94 -0
- package/lib/checks/types.js +1 -1
- package/lib/checks/unknownMagic.js +1 -1
- package/lib/checks/validator.js +12 -7
- package/lib/compiler/assert-consistency.js +12 -8
- package/lib/compiler/base.js +0 -1
- package/lib/compiler/builtins.js +42 -21
- package/lib/compiler/checks.js +46 -12
- package/lib/compiler/cycle-detector.js +1 -1
- package/lib/compiler/define.js +1103 -0
- package/lib/compiler/extend.js +983 -0
- package/lib/compiler/finalize-parse-cdl.js +231 -0
- package/lib/compiler/index.js +46 -39
- package/lib/compiler/kick-start.js +190 -0
- package/lib/compiler/moduleLayers.js +4 -4
- package/lib/compiler/populate.js +1226 -0
- package/lib/compiler/propagator.js +113 -47
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +100 -65
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +215 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +38 -25
- package/lib/edm/annotations/preprocessAnnotations.js +3 -3
- package/lib/edm/csn2edm.js +10 -9
- package/lib/edm/edm.js +19 -20
- package/lib/edm/edmPreprocessor.js +166 -95
- package/lib/edm/edmUtils.js +127 -34
- package/lib/gen/Dictionary.json +92 -43
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +11 -1
- package/lib/gen/language.tokens +86 -82
- package/lib/gen/languageLexer.interp +18 -1
- package/lib/gen/languageLexer.js +925 -847
- package/lib/gen/languageLexer.tokens +78 -74
- package/lib/gen/languageParser.js +5434 -4298
- package/lib/json/from-csn.js +59 -17
- package/lib/json/to-csn.js +189 -71
- package/lib/language/antlrParser.js +3 -3
- package/lib/language/docCommentParser.js +3 -3
- package/lib/language/errorStrategy.js +26 -8
- package/lib/language/genericAntlrParser.js +144 -53
- package/lib/language/language.g4 +424 -200
- package/lib/language/multiLineStringParser.js +536 -0
- package/lib/main.d.ts +550 -61
- package/lib/main.js +38 -11
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +322 -198
- package/lib/model/csnUtils.js +226 -370
- package/lib/model/enrichCsn.js +124 -69
- package/lib/model/revealInternalProperties.js +29 -7
- package/lib/model/sortViews.js +10 -2
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +8 -3
- package/lib/render/.eslintrc.json +1 -2
- package/lib/render/DuplicateChecker.js +1 -1
- package/lib/render/manageConstraints.js +36 -33
- package/lib/render/toCdl.js +174 -275
- package/lib/render/toHdbcds.js +203 -122
- package/lib/render/toRename.js +7 -10
- package/lib/render/toSql.js +161 -82
- package/lib/render/utils/common.js +22 -8
- package/lib/render/utils/sql.js +10 -7
- package/lib/render/utils/stringEscapes.js +111 -0
- package/lib/sql-identifier.js +1 -1
- package/lib/transform/.eslintrc.json +5 -0
- package/lib/transform/braceExpression.js +4 -2
- package/lib/transform/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +212 -0
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +187 -0
- package/lib/transform/db/cdsPersistence.js +150 -0
- package/lib/transform/db/constraints.js +61 -56
- package/lib/transform/db/expansion.js +50 -29
- package/lib/transform/db/flattening.js +556 -106
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/temporal.js +236 -0
- package/lib/transform/db/transformExists.js +103 -28
- package/lib/transform/db/views.js +92 -44
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +9 -7
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +98 -783
- package/lib/transform/forOdataNew.js +22 -175
- package/lib/transform/localized.js +36 -32
- package/lib/transform/odata/generateForeignKeyElements.js +3 -3
- package/lib/transform/odata/referenceFlattener.js +95 -89
- package/lib/transform/odata/structureFlattener.js +1 -1
- package/lib/transform/odata/toFinalBaseType.js +86 -12
- package/lib/transform/odata/typesExposure.js +5 -5
- package/lib/transform/odata/utils.js +2 -2
- package/lib/transform/transformUtilsNew.js +47 -33
- package/lib/transform/translateAssocsToJoins.js +13 -30
- package/lib/transform/universalCsn/.eslintrc.json +36 -0
- package/lib/transform/universalCsn/coreComputed.js +170 -0
- package/lib/transform/universalCsn/universalCsnEnricher.js +715 -0
- package/lib/transform/universalCsn/utils.js +63 -0
- package/lib/utils/file.js +8 -3
- package/lib/utils/objectUtils.js +30 -0
- package/lib/utils/timetrace.js +8 -2
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2349
- package/lib/compiler/resolver.js +0 -2922
- package/lib/transform/db/helpers.js +0 -58
- package/lib/transform/universalCsnEnricher.js +0 -67
package/lib/render/toHdbcds.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const {
|
|
4
|
-
|
|
4
|
+
getLastPartOf, getLastPartOfRef,
|
|
5
5
|
hasValidSkipOrExists, isBuiltinType, generatedByCompilerVersion, getNormalizedQuery,
|
|
6
|
-
getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement,
|
|
6
|
+
getRootArtifactName, getResultingName, getNamespace, forEachMember, getVariableReplacement, hasAnnotationValue,
|
|
7
7
|
} = require('../model/csnUtils');
|
|
8
8
|
const keywords = require('../base/keywords');
|
|
9
9
|
const {
|
|
10
10
|
renderFunc, beautifyExprArray, getRealName, addContextMarkers, addIntermediateContexts, cdsToSqlTypes,
|
|
11
|
-
hasHanaComment, getHanaComment, findElement, funcWithoutParen,
|
|
11
|
+
hasHanaComment, getHanaComment, findElement, funcWithoutParen, getSqlSnippets,
|
|
12
12
|
} = require('./utils/common');
|
|
13
13
|
const {
|
|
14
14
|
renderReferentialConstraint,
|
|
@@ -20,6 +20,7 @@ const { makeMessageFunction } = require('../base/messages');
|
|
|
20
20
|
const { timetrace } = require('../utils/timetrace');
|
|
21
21
|
|
|
22
22
|
const { smartId, delimitedId } = require('../sql-identifier');
|
|
23
|
+
const { ModelError } = require('../base/error');
|
|
23
24
|
|
|
24
25
|
const $PROJECTION = '$projection';
|
|
25
26
|
const $SELF = '$self';
|
|
@@ -60,8 +61,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
60
61
|
|
|
61
62
|
checkCSNVersion(csn, options);
|
|
62
63
|
|
|
63
|
-
const
|
|
64
|
-
|
|
64
|
+
const hdbcdsResult = Object.create(null);
|
|
65
65
|
|
|
66
66
|
const globalDuplicateChecker = new DuplicateChecker(options.sqlMapping); // registry for all artifact names and element names
|
|
67
67
|
|
|
@@ -100,17 +100,17 @@ function toHdbcdsSource(csn, options) {
|
|
|
100
100
|
Object.entries(art.$tableConstraints.referential)
|
|
101
101
|
.forEach(([ fileName, referentialConstraint ]) => {
|
|
102
102
|
referentialConstraints[fileName] = renderReferentialConstraint(
|
|
103
|
-
referentialConstraint, '', renderToUppercase, csn, options
|
|
103
|
+
referentialConstraint, '', renderToUppercase, csn, options
|
|
104
104
|
);
|
|
105
105
|
});
|
|
106
106
|
Object.entries(referentialConstraints)
|
|
107
|
-
.forEach(
|
|
107
|
+
.forEach(([ fileName, constraint ]) => {
|
|
108
108
|
hdbconstraint[fileName] = constraint;
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
111
|
});
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
hdbcdsResult.hdbcds = hdbcds;
|
|
113
|
+
hdbcdsResult.hdbconstraint = hdbconstraint;
|
|
114
114
|
|
|
115
115
|
if (globalDuplicateChecker)
|
|
116
116
|
globalDuplicateChecker.check(error, options); // perform duplicates check
|
|
@@ -119,7 +119,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
119
119
|
|
|
120
120
|
throwWithError();
|
|
121
121
|
timetrace.stop();
|
|
122
|
-
return options.testMode ? sort(
|
|
122
|
+
return options.testMode ? sort(hdbcdsResult) : hdbcdsResult;
|
|
123
123
|
|
|
124
124
|
/**
|
|
125
125
|
* Sort the given object alphabetically
|
|
@@ -153,7 +153,6 @@ function toHdbcdsSource(csn, options) {
|
|
|
153
153
|
|
|
154
154
|
switch (art.kind) {
|
|
155
155
|
case 'entity':
|
|
156
|
-
case 'view':
|
|
157
156
|
// FIXME: For HANA CDS, we need to replace $self at the beginning of paths in association ON-condition
|
|
158
157
|
// by the full name of the artifact we are rendering (should actually be done by forHana, but that is
|
|
159
158
|
// somewhat difficult because this kind of absolute path is quite unusual). In order not to have to pass
|
|
@@ -178,7 +177,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
178
177
|
case 'event':
|
|
179
178
|
return '';
|
|
180
179
|
default:
|
|
181
|
-
throw new
|
|
180
|
+
throw new ModelError(`Unknown artifact kind: ${art.kind}`);
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
183
|
|
|
@@ -214,7 +213,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
214
213
|
}
|
|
215
214
|
|
|
216
215
|
/**
|
|
217
|
-
* Check
|
|
216
|
+
* Check whether the given context is the direct parent of the containee.
|
|
218
217
|
*
|
|
219
218
|
* @param {string} containee Name of the contained artifact
|
|
220
219
|
* @param {string} contextName Name of the (grand?)parent context
|
|
@@ -224,7 +223,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
224
223
|
const parts = containee.split('.');
|
|
225
224
|
const prefixLength = contextName.split('.').length;
|
|
226
225
|
|
|
227
|
-
for (let i = parts.length - 1; i > prefixLength; i--
|
|
226
|
+
for (let i = parts.length - 1; i > prefixLength; i--) {
|
|
228
227
|
const prefix = parts.slice(0, i).join('.');
|
|
229
228
|
const art = csn.definitions[prefix];
|
|
230
229
|
if (art && (art.kind === 'context' || art.kind === 'service'))
|
|
@@ -291,7 +290,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
291
290
|
function renderContext(artifactName, art, env, isShadowed) {
|
|
292
291
|
let result = '';
|
|
293
292
|
if (!isShadowed)
|
|
294
|
-
isShadowed = contextIsShadowed(artifactName
|
|
293
|
+
isShadowed = contextIsShadowed(artifactName);
|
|
295
294
|
if (isShadowed) {
|
|
296
295
|
const subArtifacts = getSubArtifacts(artifactName);
|
|
297
296
|
for (const name in subArtifacts)
|
|
@@ -314,14 +313,13 @@ function toHdbcdsSource(csn, options) {
|
|
|
314
313
|
return `${result + renderedSubArtifacts + env.indent}};\n`;
|
|
315
314
|
}
|
|
316
315
|
/**
|
|
317
|
-
* Check
|
|
316
|
+
* Check whether the given context is shadowed, i.e. part of his name prefix is shared by a
|
|
318
317
|
* non-context/service/namespace definition
|
|
319
318
|
*
|
|
320
319
|
* @param {string} artifactName
|
|
321
|
-
* @param {CSN.Model} csn
|
|
322
320
|
* @returns {boolean}
|
|
323
321
|
*/
|
|
324
|
-
function contextIsShadowed(artifactName
|
|
322
|
+
function contextIsShadowed(artifactName) {
|
|
325
323
|
if (artifactName.indexOf('.') === -1)
|
|
326
324
|
return false;
|
|
327
325
|
|
|
@@ -395,6 +393,12 @@ function toHdbcdsSource(csn, options) {
|
|
|
395
393
|
if (hasHanaComment(art, options))
|
|
396
394
|
result += `${env.indent}@Comment: '${getEscapedHanaComment(art)}'\n`;
|
|
397
395
|
|
|
396
|
+
// tables can have @sql.prepend and @sql.append
|
|
397
|
+
const { front, back } = getSqlSnippets(options, art);
|
|
398
|
+
|
|
399
|
+
if (front) // attach @sql.prepend after adding @Comment annotation
|
|
400
|
+
result += front;
|
|
401
|
+
|
|
398
402
|
result += `${env.indent + (art.abstract ? 'abstract ' : '')}entity ${normalizedArtifactName}`;
|
|
399
403
|
if (art.includes) {
|
|
400
404
|
// Includes are never flattened (don't exist in HANA)
|
|
@@ -412,8 +416,12 @@ function toHdbcdsSource(csn, options) {
|
|
|
412
416
|
|
|
413
417
|
duplicateChecker.check(error);
|
|
414
418
|
result += `${env.indent}}`;
|
|
415
|
-
result += `${renderTechnicalConfiguration(art.technicalConfig, env)}
|
|
416
|
-
|
|
419
|
+
result += `${renderTechnicalConfiguration(art.technicalConfig, env)}`;
|
|
420
|
+
|
|
421
|
+
if (back)
|
|
422
|
+
result += back;
|
|
423
|
+
|
|
424
|
+
return `${result};\n`;
|
|
417
425
|
}
|
|
418
426
|
|
|
419
427
|
/**
|
|
@@ -429,9 +437,8 @@ function toHdbcdsSource(csn, options) {
|
|
|
429
437
|
if (!element.target)
|
|
430
438
|
return;
|
|
431
439
|
|
|
432
|
-
let alias = element['@cds.persistence.name'];
|
|
433
440
|
if (uppercaseAndUnderscore(element.target) === element['@cds.persistence.name']) {
|
|
434
|
-
alias = createTopLevelAliasName(element['@cds.persistence.name']);
|
|
441
|
+
let alias = createTopLevelAliasName(element['@cds.persistence.name']);
|
|
435
442
|
// calculate new alias if it would conflict with other csn.Artifact
|
|
436
443
|
while (csn.definitions[alias])
|
|
437
444
|
alias = createTopLevelAliasName(alias);
|
|
@@ -462,7 +469,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
462
469
|
// in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
|
|
463
470
|
tc = tc.hana;
|
|
464
471
|
if (!tc)
|
|
465
|
-
throw new
|
|
472
|
+
throw new ModelError('Expecting a HANA technical configuration');
|
|
466
473
|
|
|
467
474
|
result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
|
|
468
475
|
|
|
@@ -537,7 +544,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
537
544
|
* @param {CSN.Element} elm Content of the element
|
|
538
545
|
* @param {CdlRenderEnvironment} env Environment
|
|
539
546
|
* @param {DuplicateChecker} [duplicateChecker] Utility for detecting duplicates
|
|
540
|
-
* @param {boolean} [isSubElement]
|
|
547
|
+
* @param {boolean} [isSubElement] Whether the given element is a subelement or not - subelements cannot be key!
|
|
541
548
|
* @returns {string} The rendered element
|
|
542
549
|
*/
|
|
543
550
|
function renderElement(elementName, elm, env, duplicateChecker, isSubElement) {
|
|
@@ -556,13 +563,19 @@ function toHdbcdsSource(csn, options) {
|
|
|
556
563
|
result += `${env.indent}@Comment: '${getEscapedHanaComment(elm)}'\n`;
|
|
557
564
|
|
|
558
565
|
result += env.indent + (elm.key && !isSubElement ? 'key ' : '') +
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
566
|
+
(elm.masked ? 'masked ' : '') +
|
|
567
|
+
quotedElementName + (omitColon ? ' ' : ' : ') +
|
|
568
|
+
renderTypeReference(elm, env) +
|
|
569
|
+
renderNullability(elm);
|
|
563
570
|
if (elm.default)
|
|
564
571
|
result += ` default ${renderExpr(elm.default, env)}`;
|
|
565
572
|
|
|
573
|
+
// (table) elements can only have a @sql.append
|
|
574
|
+
const { back } = getSqlSnippets(options, elm);
|
|
575
|
+
|
|
576
|
+
if (back)
|
|
577
|
+
result += back;
|
|
578
|
+
|
|
566
579
|
return `${result};\n`;
|
|
567
580
|
}
|
|
568
581
|
|
|
@@ -615,7 +628,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
615
628
|
function renderAbsolutePath(path, env) {
|
|
616
629
|
// Sanity checks
|
|
617
630
|
if (!path.ref)
|
|
618
|
-
throw new
|
|
631
|
+
throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
|
|
619
632
|
|
|
620
633
|
|
|
621
634
|
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
@@ -760,7 +773,14 @@ function toHdbcdsSource(csn, options) {
|
|
|
760
773
|
}
|
|
761
774
|
env._artifact = art;
|
|
762
775
|
result += renderQuery(getNormalizedQuery(art).query, true, env, artifactPath.concat(art.projection ? 'projection' : 'query'), art.elements);
|
|
776
|
+
|
|
777
|
+
// views can only have a @sql.append
|
|
778
|
+
const { back } = getSqlSnippets(options, art);
|
|
779
|
+
if (back)
|
|
780
|
+
result += back;
|
|
781
|
+
|
|
763
782
|
result += ';\n';
|
|
783
|
+
|
|
764
784
|
return result;
|
|
765
785
|
}
|
|
766
786
|
|
|
@@ -771,7 +791,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
771
791
|
* or 'entity')
|
|
772
792
|
*
|
|
773
793
|
* @param {CSN.Query} query Query object
|
|
774
|
-
* @param {boolean} isLeadingQuery
|
|
794
|
+
* @param {boolean} isLeadingQuery Whether the query is the leading query or not
|
|
775
795
|
* @param {CdlRenderEnvironment} env Environment
|
|
776
796
|
* @param {CSN.Path} [path=[]] CSN path to the query
|
|
777
797
|
* @param {object} [elements] For leading query, the elements of the artifact
|
|
@@ -803,7 +823,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
803
823
|
}
|
|
804
824
|
// Otherwise must have a SELECT
|
|
805
825
|
else if (!query.SELECT) {
|
|
806
|
-
throw new
|
|
826
|
+
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
807
827
|
}
|
|
808
828
|
const select = query.SELECT;
|
|
809
829
|
const childEnv = increaseIndent(env);
|
|
@@ -829,72 +849,73 @@ function toHdbcdsSource(csn, options) {
|
|
|
829
849
|
result += `${env.indent}}`;
|
|
830
850
|
}
|
|
831
851
|
if (select.excluding) {
|
|
832
|
-
|
|
852
|
+
const excludingList = select.excluding.map(id => `${childEnv.indent}${formatIdentifier(id)}`).join(',\n');
|
|
853
|
+
result += ` excluding {\n${excludingList}\n`;
|
|
833
854
|
result += `${env.indent}}`;
|
|
834
855
|
}
|
|
835
856
|
|
|
836
|
-
return renderSelectProperties(select, result);
|
|
837
|
-
|
|
838
|
-
/**
|
|
839
|
-
* Render WHERE, GROUP BY, HAVING, ORDER BY and LIMIT clause
|
|
840
|
-
*
|
|
841
|
-
* @param {CSN.QuerySelect} select
|
|
842
|
-
* @param {string} alreadyRendered The query as it has been rendered so far
|
|
843
|
-
* @returns {string} The query with WHERE etc. added
|
|
844
|
-
*/
|
|
845
|
-
function renderSelectProperties(select, alreadyRendered) {
|
|
846
|
-
if (select.where)
|
|
847
|
-
alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env, true, true)}`;
|
|
857
|
+
return renderSelectProperties(select, result, env);
|
|
858
|
+
}
|
|
848
859
|
|
|
849
|
-
|
|
850
|
-
|
|
860
|
+
/**
|
|
861
|
+
* Render WHERE, GROUP BY, HAVING, ORDER BY and LIMIT clause
|
|
862
|
+
*
|
|
863
|
+
* @param {CSN.QuerySelect} select
|
|
864
|
+
* @param {string} alreadyRendered The query as it has been rendered so far
|
|
865
|
+
* @param {CdlRenderEnvironment} env Environment
|
|
866
|
+
* @returns {string} The query with WHERE etc. added
|
|
867
|
+
*/
|
|
868
|
+
function renderSelectProperties(select, alreadyRendered, env) {
|
|
869
|
+
if (select.where)
|
|
870
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env, true, true)}`;
|
|
851
871
|
|
|
852
|
-
|
|
853
|
-
|
|
872
|
+
if (select.groupBy)
|
|
873
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
|
|
854
874
|
|
|
855
|
-
|
|
856
|
-
|
|
875
|
+
if (select.having)
|
|
876
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env, true, true)}`;
|
|
857
877
|
|
|
858
|
-
|
|
859
|
-
|
|
878
|
+
if (select.orderBy)
|
|
879
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}order by ${select.orderBy.map(entry => renderOrderByEntry(entry, env)).join(', ')}`;
|
|
860
880
|
|
|
861
|
-
|
|
862
|
-
|
|
881
|
+
if (select.limit)
|
|
882
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}${renderLimit(select.limit, env)}`;
|
|
863
883
|
|
|
884
|
+
return alreadyRendered;
|
|
885
|
+
}
|
|
864
886
|
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
}
|
|
877
|
-
// Otherwise, start new line and indent normally
|
|
878
|
-
return `\n${increaseIndent(env).indent}`;
|
|
887
|
+
/**
|
|
888
|
+
* Utility function to make sure that we continue with the same indentation in WHERE, GROUP BY, ... after a closing curly brace and beyond
|
|
889
|
+
*
|
|
890
|
+
* @param {string} result Result of a previous render step
|
|
891
|
+
* @param {CdlRenderEnvironment} env Environment
|
|
892
|
+
* @returns {string} String to join with
|
|
893
|
+
*/
|
|
894
|
+
function continueIndent(result, env) {
|
|
895
|
+
if (result.endsWith('}') || result.endsWith('})')) {
|
|
896
|
+
// The preceding clause ended with '}', just append after that
|
|
897
|
+
return ' ';
|
|
879
898
|
}
|
|
899
|
+
// Otherwise, start new line and indent normally
|
|
900
|
+
return `\n${increaseIndent(env).indent}`;
|
|
901
|
+
}
|
|
880
902
|
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
903
|
+
/**
|
|
904
|
+
* Render a query's LIMIT clause, which may have also have OFFSET.
|
|
905
|
+
*
|
|
906
|
+
* @param {CSN.QueryLimit} limit CSN limit clause
|
|
907
|
+
* @param {CdlRenderEnvironment} env Environment
|
|
908
|
+
* @returns {string} Rendered limit clause
|
|
909
|
+
*/
|
|
910
|
+
function renderLimit(limit, env) {
|
|
911
|
+
let result = '';
|
|
912
|
+
if (limit.rows !== undefined)
|
|
913
|
+
result += `limit ${renderExpr(limit.rows, env)}`;
|
|
892
914
|
|
|
893
|
-
|
|
894
|
-
|
|
915
|
+
if (limit.offset !== undefined)
|
|
916
|
+
result += `${result !== '' ? `\n${increaseIndent(env).indent}` : ''}offset ${renderExpr(limit.offset, env)}`;
|
|
895
917
|
|
|
896
|
-
|
|
897
|
-
}
|
|
918
|
+
return result;
|
|
898
919
|
}
|
|
899
920
|
|
|
900
921
|
/**
|
|
@@ -906,7 +927,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
906
927
|
* @returns {string} Rendered order by
|
|
907
928
|
*/
|
|
908
929
|
function renderOrderByEntry(entry, env) {
|
|
909
|
-
let result = renderExpr(entry, env);
|
|
930
|
+
let result = renderExpr(entry, env, true, false, true);
|
|
910
931
|
if (entry.sort)
|
|
911
932
|
result += ` ${entry.sort}`;
|
|
912
933
|
|
|
@@ -926,7 +947,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
926
947
|
*/
|
|
927
948
|
function renderParameter(parName, par, env) {
|
|
928
949
|
if (par.notNull === true || par.notNull === false)
|
|
929
|
-
info(
|
|
950
|
+
info('query-ignoring-param-nullability', env.path.concat([ 'params', parName ]), { '#': 'std' });
|
|
930
951
|
return `${env.indent + formatParamIdentifier(parName, env.path.concat([ 'params', parName ]))} : ${renderTypeReference(par, env)}`;
|
|
931
952
|
}
|
|
932
953
|
|
|
@@ -943,7 +964,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
943
964
|
if (art.kind === 'aspect' || art.kind === 'type' && !hdbcdsNames || art.kind === 'type' && hdbcdsNames && !art.elements)
|
|
944
965
|
return '';
|
|
945
966
|
let result = '';
|
|
946
|
-
result += `${env.indent + (art.kind
|
|
967
|
+
result += `${env.indent + (art.kind)} ${renderArtifactName(artifactName, env, true)}`;
|
|
947
968
|
if (art.includes) {
|
|
948
969
|
// Includes are never flattened (don't exist in HANA)
|
|
949
970
|
result += ` : ${art.includes.map(name => renderAbsoluteNameWithQuotes(name, env)).join(', ')}`;
|
|
@@ -968,7 +989,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
968
989
|
* Render a reference to a type used by 'elm' (named or inline)
|
|
969
990
|
* Allow suppressing enum-rendering - used in columns for example
|
|
970
991
|
*
|
|
971
|
-
* @param {
|
|
992
|
+
* @param {object} elm Element using the type reference
|
|
972
993
|
* @param {CdlRenderEnvironment} env Environment
|
|
973
994
|
* @returns {string} Rendered type reference
|
|
974
995
|
*/
|
|
@@ -990,7 +1011,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
990
1011
|
// Anonymous structured type
|
|
991
1012
|
if (!elm.type) {
|
|
992
1013
|
if (!elm.elements)
|
|
993
|
-
throw new
|
|
1014
|
+
throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
|
|
994
1015
|
|
|
995
1016
|
result += '{\n';
|
|
996
1017
|
const childEnv = increaseIndent(env);
|
|
@@ -1091,33 +1112,36 @@ function toHdbcdsSource(csn, options) {
|
|
|
1091
1112
|
* Render an expression (including paths and values) or condition 'x'.
|
|
1092
1113
|
* (no trailing LF, don't indent if inline)
|
|
1093
1114
|
*
|
|
1094
|
-
* @param {any}
|
|
1115
|
+
* @param {any} expr Expression to render
|
|
1095
1116
|
* @param {CdlRenderEnvironment} env Environment
|
|
1096
|
-
* @param {boolean} [inline=true]
|
|
1117
|
+
* @param {boolean} [inline=true] Whether to render inline
|
|
1097
1118
|
* @param {boolean} [inExpr=false] Whether the expression is already inside another expression
|
|
1119
|
+
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
|
|
1120
|
+
* Note: This is a hack for casts() inside groupBy.
|
|
1098
1121
|
* @returns {string} Rendered expression
|
|
1099
1122
|
*/
|
|
1100
|
-
function renderExpr(
|
|
1123
|
+
function renderExpr(expr, env, inline = true, inExpr = false, alwaysRenderCast = false) {
|
|
1101
1124
|
// Compound expression
|
|
1102
|
-
if (Array.isArray(
|
|
1103
|
-
return beautifyExprArray(
|
|
1125
|
+
if (Array.isArray(expr))
|
|
1126
|
+
return beautifyExprArray(expr.map(item => renderExpr(item, env, inline, inExpr)));
|
|
1104
1127
|
|
|
1105
|
-
if (typeof
|
|
1106
|
-
if (inExpr &&
|
|
1107
|
-
return renderExplicitTypeCast(renderExprObject());
|
|
1108
|
-
return renderExprObject();
|
|
1128
|
+
if (typeof expr === 'object' && expr !== null) {
|
|
1129
|
+
if ((inExpr || alwaysRenderCast) && expr.cast && expr.cast.type)
|
|
1130
|
+
return renderExplicitTypeCast(renderExprObject(expr));
|
|
1131
|
+
return renderExprObject(expr);
|
|
1109
1132
|
}
|
|
1110
1133
|
|
|
1111
1134
|
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
1112
|
-
return
|
|
1135
|
+
return expr;
|
|
1113
1136
|
|
|
1114
1137
|
|
|
1115
1138
|
/**
|
|
1116
1139
|
* Various special cases represented as objects
|
|
1117
1140
|
*
|
|
1141
|
+
* @param {object} x Expression
|
|
1118
1142
|
* @returns {string} Rendered expression object
|
|
1119
1143
|
*/
|
|
1120
|
-
function renderExprObject() {
|
|
1144
|
+
function renderExprObject(x) {
|
|
1121
1145
|
if (x.list) {
|
|
1122
1146
|
// set "inExpr" to false: treat list elements as new expressions
|
|
1123
1147
|
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
|
|
@@ -1145,7 +1169,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1145
1169
|
// we can't quote functions with parens, issue warning if it is a reserved keyword
|
|
1146
1170
|
if (!funcWithoutParen(x, 'hana') && keywords.hdbcds.includes(uppercaseAndUnderscore(funcName)))
|
|
1147
1171
|
warning(null, x.$location, `The identifier “${uppercaseAndUnderscore(funcName)}“ is a SAP HANA keyword`);
|
|
1148
|
-
return renderFunc(
|
|
1172
|
+
return renderFunc(funcName, x, 'hana', a => renderArgs(a, '=>', env));
|
|
1149
1173
|
}
|
|
1150
1174
|
// Nested expression
|
|
1151
1175
|
else if (x.xpr) {
|
|
@@ -1164,7 +1188,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1164
1188
|
return `${renderQuery(x, false, increaseIndent(env))}`;
|
|
1165
1189
|
}
|
|
1166
1190
|
|
|
1167
|
-
throw new
|
|
1191
|
+
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1168
1192
|
}
|
|
1169
1193
|
/**
|
|
1170
1194
|
* @param {object} x Expression with a val and/or literal property
|
|
@@ -1190,7 +1214,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1190
1214
|
|
|
1191
1215
|
// otherwise fall through to
|
|
1192
1216
|
default:
|
|
1193
|
-
throw new
|
|
1217
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1194
1218
|
}
|
|
1195
1219
|
}
|
|
1196
1220
|
|
|
@@ -1242,10 +1266,10 @@ function toHdbcdsSource(csn, options) {
|
|
|
1242
1266
|
* @returns {string} Rendered cast()
|
|
1243
1267
|
*/
|
|
1244
1268
|
function renderExplicitTypeCast(value) {
|
|
1245
|
-
let typeRef = renderTypeReference(
|
|
1269
|
+
let typeRef = renderTypeReference(expr.cast, env);
|
|
1246
1270
|
|
|
1247
1271
|
// inside a cast expression, the cds and hana cds types need to be mapped to hana sql types
|
|
1248
|
-
const hanaSqlType = cdsToSqlTypes.hana[
|
|
1272
|
+
const hanaSqlType = cdsToSqlTypes.hana[expr.cast.type] || cdsToSqlTypes.standard[expr.cast.type];
|
|
1249
1273
|
if (hanaSqlType) {
|
|
1250
1274
|
const typeRefWithoutParams = typeRef.substring(0, typeRef.indexOf('(')) || typeRef;
|
|
1251
1275
|
typeRef = typeRef.replace(typeRefWithoutParams, hanaSqlType);
|
|
@@ -1268,7 +1292,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1268
1292
|
// (see FIXME at renderArtifact)
|
|
1269
1293
|
if (idx === 0 && s === $SELF) {
|
|
1270
1294
|
// do not produce USING for $projection
|
|
1271
|
-
if (env.currentArtifactName === $PROJECTION
|
|
1295
|
+
if (env.currentArtifactName === $PROJECTION)
|
|
1272
1296
|
return env.currentArtifactName;
|
|
1273
1297
|
|
|
1274
1298
|
return plainNames ? renderAbsoluteNamePlain(env.currentArtifactName, env)
|
|
@@ -1297,7 +1321,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1297
1321
|
else if (typeof s === 'object') {
|
|
1298
1322
|
// Sanity check
|
|
1299
1323
|
if (!s.func && !s.id)
|
|
1300
|
-
throw new
|
|
1324
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1301
1325
|
|
|
1302
1326
|
// Not really a path step but an object-like function call
|
|
1303
1327
|
if (s.func)
|
|
@@ -1316,7 +1340,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1316
1340
|
return result;
|
|
1317
1341
|
}
|
|
1318
1342
|
|
|
1319
|
-
throw new
|
|
1343
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1320
1344
|
}
|
|
1321
1345
|
}
|
|
1322
1346
|
|
|
@@ -1333,15 +1357,15 @@ function toHdbcdsSource(csn, options) {
|
|
|
1333
1357
|
const args = node.args ? node.args : {};
|
|
1334
1358
|
// Positional arguments
|
|
1335
1359
|
if (Array.isArray(args))
|
|
1336
|
-
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1360
|
+
return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
|
|
1337
1361
|
|
|
1338
1362
|
// Named arguments (object/dict)
|
|
1339
1363
|
else if (typeof args === 'object')
|
|
1340
1364
|
// if this is a function param which is not a reference to the model, we must not quote it
|
|
1341
|
-
return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
|
|
1365
|
+
return Object.keys(args).map(key => `${node.func ? key : formatIdentifier(key)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
|
|
1342
1366
|
|
|
1343
1367
|
|
|
1344
|
-
throw new
|
|
1368
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1345
1369
|
}
|
|
1346
1370
|
|
|
1347
1371
|
/**
|
|
@@ -1521,9 +1545,70 @@ function toHdbcdsSource(csn, options) {
|
|
|
1521
1545
|
Object.keys(env.topLevelAliases)
|
|
1522
1546
|
.filter(name => env.topLevelAliases[name].quotedAlias !== formatIdentifier(uppercaseAndUnderscore(artifactName))) // avoid "using FOO as FOO" in FOO.cds
|
|
1523
1547
|
.forEach((name) => {
|
|
1548
|
+
const nativeObjectExists = csn.definitions[name] && hasAnnotationValue(csn.definitions[name], '@cds.persistence.exists');
|
|
1549
|
+
if (!plainNames && nativeObjectExists)
|
|
1550
|
+
checkForNameClashesWithNativeObject(name);
|
|
1524
1551
|
distinct[`using ${env.topLevelAliases[name].quotedName} as ${env.topLevelAliases[name].quotedAlias};\n`] = '';
|
|
1525
1552
|
});
|
|
1553
|
+
/**
|
|
1554
|
+
* If we generate a `using <native object> from <bar>` clause,
|
|
1555
|
+
* we warn if we generate a SAP HANA CDS artifact which would hide the
|
|
1556
|
+
* native DB object from being found by the SAP HANA CDS compiler
|
|
1557
|
+
* see cap/cds-compiler#8269 for details
|
|
1558
|
+
* @param {string} name of the native db object
|
|
1559
|
+
*/
|
|
1560
|
+
function checkForNameClashesWithNativeObject(name) {
|
|
1561
|
+
const possibleShadowName = getNamePrefix(env.topLevelAliases[name].quotedName);
|
|
1562
|
+
const mightBeShadowedBy = csn.definitions[possibleShadowName];
|
|
1563
|
+
if (mightBeShadowedBy) {
|
|
1564
|
+
const artifactWillBeRendered = isArtifactRendered(mightBeShadowedBy, possibleShadowName);
|
|
1565
|
+
// only warn if actually rendered to HANA CDS
|
|
1566
|
+
if (artifactWillBeRendered)
|
|
1567
|
+
warning('anno-hidden-exists', [ 'definitions', name ], { name: possibleShadowName }, 'Native database object is hidden by a definition starting with $(NAME)');
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
function isArtifactRendered(art, artName) {
|
|
1572
|
+
const isHanaCdsContext = art.kind === 'service' || art.kind === 'context';
|
|
1573
|
+
if (isHanaCdsContext)
|
|
1574
|
+
return isContextRendered(artName);
|
|
1575
|
+
if ([ 'action', 'function', 'event' ].includes(art.kind) || options.sqlMapping !== 'hdbcds' && art.kind === 'type')
|
|
1576
|
+
return false;
|
|
1577
|
+
return !(hasAnnotationValue(art, '@cds.persistence.exists') || hasAnnotationValue(art, '@cds.persistence.skip'));
|
|
1578
|
+
}
|
|
1579
|
+
|
|
1580
|
+
/**
|
|
1581
|
+
* Check if there is at least one entity which will be rendered as SAP HANA CDS entity
|
|
1582
|
+
* inside the given context (or in its sub-contexts).
|
|
1583
|
+
* Or in other words: If the context will be rendered as a SAP HANA CDS context in the end.
|
|
1584
|
+
*
|
|
1585
|
+
* @param {string} contextName
|
|
1586
|
+
* @returns {boolean} true if a context/service will be rendered as a SAP HANA CDS context.
|
|
1587
|
+
*/
|
|
1588
|
+
function isContextRendered(contextName) {
|
|
1589
|
+
const subArtifacts = getSubArtifacts(contextName);
|
|
1590
|
+
return Object.entries(subArtifacts).some(([ artName, art ]) => {
|
|
1591
|
+
if (art.kind === 'context')
|
|
1592
|
+
return isContextRendered(`${contextName}.${artName}`);
|
|
1593
|
+
return isArtifactRendered(art, artName);
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1526
1596
|
|
|
1597
|
+
/**
|
|
1598
|
+
* @param {string} usingName the string which appears in the `using <string> from ..` including the quotes
|
|
1599
|
+
* @returns the prefix of the `using` name.
|
|
1600
|
+
* @example
|
|
1601
|
+
* "com.sap.foo.native.object" --> com
|
|
1602
|
+
* "com.sap.foo::native.object" --> com.sap.foo.native
|
|
1603
|
+
*/
|
|
1604
|
+
function getNamePrefix(usingName) {
|
|
1605
|
+
usingName = usingName.replace(/"/g, '');
|
|
1606
|
+
if (usingName.indexOf('::') !== -1) {
|
|
1607
|
+
const parts = usingName.split('::');
|
|
1608
|
+
return `${parts[0]}.${parts[1].split('.')[0]}`;
|
|
1609
|
+
}
|
|
1610
|
+
return usingName.split('.')[0];
|
|
1611
|
+
}
|
|
1527
1612
|
return Object.keys(distinct).join('');
|
|
1528
1613
|
}
|
|
1529
1614
|
|
|
@@ -1562,7 +1647,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1562
1647
|
if (plainNames) {
|
|
1563
1648
|
const art = csn.definitions[name];
|
|
1564
1649
|
// For 'plain' naming, take all entities and views, nothing else
|
|
1565
|
-
if (art.kind === 'entity'
|
|
1650
|
+
if (art.kind === 'entity')
|
|
1566
1651
|
result[name] = art;
|
|
1567
1652
|
}
|
|
1568
1653
|
else {
|
|
@@ -1653,12 +1738,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1653
1738
|
function quoteId(id) {
|
|
1654
1739
|
// Should only ever be called for real IDs (i.e. no dots inside)
|
|
1655
1740
|
if (id.indexOf('.') !== -1)
|
|
1656
|
-
throw new
|
|
1657
|
-
|
|
1658
|
-
// FIXME: Somewhat arbitrary magic: Do not quote $projection (because HANA CDS doesn't recognize it otherwise). Similar for $self.
|
|
1659
|
-
// FIXME: The test should not be on the name, but by checking the _artifact.
|
|
1660
|
-
if (id === $PROJECTION || id === $SELF)
|
|
1661
|
-
return id;
|
|
1741
|
+
throw new ModelError(id);
|
|
1662
1742
|
|
|
1663
1743
|
|
|
1664
1744
|
switch (options.forHana.names) {
|
|
@@ -1681,13 +1761,14 @@ function toHdbcdsSource(csn, options) {
|
|
|
1681
1761
|
* @returns {string} Correctly quoted absname
|
|
1682
1762
|
*/
|
|
1683
1763
|
function quoteAbsoluteNameAsId(absname) {
|
|
1764
|
+
const resultingName = getResultingName(csn, options.sqlMapping, absname);
|
|
1765
|
+
|
|
1684
1766
|
if (hdbcdsNames) {
|
|
1685
|
-
const
|
|
1686
|
-
const namespace = getParentNameOf(topLevelName);
|
|
1767
|
+
const namespace = getNamespace(csn, absname);
|
|
1687
1768
|
if (namespace)
|
|
1688
|
-
return `"${(`${namespace}::${
|
|
1769
|
+
return `"${(`${namespace}::${resultingName.substring(namespace.length + 2)}`).replace(/"/g, '""')}"`;
|
|
1689
1770
|
}
|
|
1690
|
-
return `"${
|
|
1771
|
+
return `"${resultingName.replace(/"/g, '""')}"`;
|
|
1691
1772
|
}
|
|
1692
1773
|
|
|
1693
1774
|
/**
|
|
@@ -1742,7 +1823,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1742
1823
|
function renderArtifactName(artifactName, env, fallthrough = false) {
|
|
1743
1824
|
if (plainNames && !fallthrough)
|
|
1744
1825
|
return formatIdentifier(uppercaseAndUnderscore(artifactName));
|
|
1745
|
-
|
|
1826
|
+
// hdbcds with quoted or hdbcds naming
|
|
1746
1827
|
return env.namePrefix + quoteId(getRealName(csn, artifactName).replace(/\./g, '_'));
|
|
1747
1828
|
}
|
|
1748
1829
|
|