@sap/cds-compiler 2.13.8 → 3.0.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 +155 -1594
- package/bin/cdsc.js +144 -66
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +3 -4
- package/doc/CHANGELOG_DEPRECATED.md +35 -1
- package/doc/{DeprecatedOptions.md → DeprecatedOptions_v2.md} +3 -1
- package/doc/Versioning.md +20 -1
- package/lib/api/.eslintrc.json +2 -2
- package/lib/api/main.js +237 -122
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +12 -16
- package/lib/base/keywords.js +216 -109
- package/lib/base/message-registry.js +152 -37
- package/lib/base/messages.js +145 -83
- package/lib/base/model.js +44 -2
- package/lib/base/optionProcessorHelper.js +19 -0
- package/lib/checks/actionsFunctions.js +7 -5
- package/lib/checks/annotationsOData.js +11 -32
- package/lib/checks/arrayOfs.js +1 -34
- package/lib/checks/cdsPersistence.js +1 -0
- package/lib/checks/elements.js +6 -6
- package/lib/checks/invalidTarget.js +1 -1
- package/lib/checks/nonexpandableStructured.js +1 -1
- package/lib/checks/queryNoDbArtifacts.js +2 -1
- package/lib/checks/selectItems.js +5 -1
- package/lib/checks/types.js +4 -2
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +4 -5
- package/lib/compiler/assert-consistency.js +16 -10
- package/lib/compiler/base.js +1 -0
- package/lib/compiler/builtins.js +98 -9
- package/lib/compiler/checks.js +22 -70
- package/lib/compiler/define.js +61 -13
- package/lib/compiler/extend.js +79 -14
- package/lib/compiler/finalize-parse-cdl.js +46 -29
- package/lib/compiler/index.js +100 -37
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +19 -18
- package/lib/compiler/propagator.js +7 -4
- package/lib/compiler/resolve.js +297 -234
- package/lib/compiler/shared.js +107 -102
- package/lib/compiler/tweak-assocs.js +16 -11
- package/lib/compiler/utils.js +5 -0
- package/lib/edm/annotations/genericTranslation.js +93 -21
- package/lib/edm/csn2edm.js +230 -115
- package/lib/edm/edm.js +305 -226
- package/lib/edm/edmPreprocessor.js +509 -438
- package/lib/edm/edmUtils.js +31 -45
- package/lib/gen/Dictionary.json +98 -22
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +10 -30
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +889 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20786 -22199
- package/lib/json/csnVersion.js +10 -11
- package/lib/json/from-csn.js +59 -51
- package/lib/json/to-csn.js +10 -10
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +62 -39
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +348 -229
- package/lib/language/language.g4 +629 -653
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +46 -43
- package/lib/main.js +108 -79
- package/lib/model/csnRefs.js +34 -7
- package/lib/model/csnUtils.js +337 -332
- package/lib/model/enrichCsn.js +1 -0
- package/lib/model/revealInternalProperties.js +30 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +6 -6
- package/lib/optionProcessor.js +73 -46
- package/lib/render/.eslintrc.json +1 -1
- package/lib/render/DuplicateChecker.js +4 -7
- package/lib/render/manageConstraints.js +70 -2
- package/lib/render/toCdl.js +1042 -882
- package/lib/render/toHdbcds.js +195 -245
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +225 -241
- package/lib/render/utils/common.js +145 -15
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +4 -3
- package/lib/transform/db/associations.js +2 -2
- package/lib/transform/db/cdsPersistence.js +5 -15
- package/lib/transform/db/constraints.js +4 -2
- package/lib/transform/db/expansion.js +22 -16
- package/lib/transform/db/flattening.js +109 -80
- package/lib/transform/db/transformExists.js +7 -7
- package/lib/transform/db/views.js +9 -6
- package/lib/transform/draft/.eslintrc.json +2 -2
- package/lib/transform/draft/db.js +6 -6
- package/lib/transform/draft/odata.js +6 -7
- package/lib/transform/forHanaNew.js +62 -48
- package/lib/transform/forOdataNew.js +49 -50
- package/lib/transform/localized.js +31 -20
- package/lib/transform/odata/toFinalBaseType.js +16 -14
- package/lib/transform/odata/typesExposure.js +146 -198
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +67 -84
- package/lib/transform/translateAssocsToJoins.js +7 -3
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +16 -9
- package/lib/transform/universalCsn/universalCsnEnricher.js +60 -10
- package/lib/utils/file.js +3 -3
- package/lib/utils/moduleResolve.js +13 -6
- package/lib/utils/timetrace.js +20 -21
- package/package.json +35 -4
- package/share/messages/message-explanations.json +2 -1
- package/share/messages/syntax-expected-integer.md +37 -0
- package/doc/ApiMigration.md +0 -237
- package/doc/CommandLineMigration.md +0 -58
- package/doc/ErrorMessages.md +0 -175
- package/doc/FioriAnnotations.md +0 -94
- package/doc/ODataTransformation.md +0 -273
- package/lib/backends.js +0 -529
- package/lib/fix_antlr4-8_warning.js +0 -56
- package/lib/transform/odata/attachPath.js +0 -96
- package/lib/transform/odata/expandStructKeysInAssociations.js +0 -59
- package/lib/transform/odata/generateForeignKeyElements.js +0 -261
- package/lib/transform/odata/referenceFlattener.js +0 -296
- package/lib/transform/odata/sortByAssociationDependency.js +0 -105
- package/lib/transform/odata/structuralPath.js +0 -72
- package/lib/transform/odata/structureFlattener.js +0 -171
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
const { ModelError } = require('../../base/error');
|
|
6
|
+
|
|
5
7
|
const functionsWithoutParams = {
|
|
6
8
|
hana: {
|
|
7
9
|
CURRENT_CONNECTION: {},
|
|
@@ -277,15 +279,27 @@ const cdsToSqlTypes = {
|
|
|
277
279
|
'cds.hana.BINARY': 'BINARY',
|
|
278
280
|
'cds.hana.SMALLDECIMAL': 'DECIMAL',
|
|
279
281
|
},
|
|
282
|
+
postgres: {
|
|
283
|
+
// TODO: Type mapping for binary types is not correct, yet.
|
|
284
|
+
// We can't use text types for binary on PostgreSQL due to NUL!
|
|
285
|
+
'cds.String': 'VARCHAR',
|
|
286
|
+
'cds.LargeString': 'text',
|
|
287
|
+
'cds.hana.CLOB': 'text',
|
|
288
|
+
'cds.LargeBinary': 'bytea',
|
|
289
|
+
'cds.Binary': 'VARCHAR',
|
|
290
|
+
'cds.hana.BINARY': 'VARCHAR',
|
|
291
|
+
'cds.Double': 'double precision',
|
|
292
|
+
'cds.hana.TINYINT': 'INTEGER',
|
|
293
|
+
},
|
|
280
294
|
};
|
|
281
295
|
|
|
282
296
|
/**
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
297
|
+
* Get the element matching the column
|
|
298
|
+
*
|
|
299
|
+
* @param {CSN.Elements} elements Elements of a query
|
|
300
|
+
* @param {CSN.Column} column Column from the same query
|
|
301
|
+
* @returns {CSN.Element}
|
|
302
|
+
*/
|
|
289
303
|
function findElement(elements, column) {
|
|
290
304
|
if (!elements)
|
|
291
305
|
return undefined;
|
|
@@ -341,16 +355,17 @@ function hasHanaComment(obj, options) {
|
|
|
341
355
|
return !options.disableHanaComments && typeof obj.doc === 'string';
|
|
342
356
|
}
|
|
343
357
|
/**
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
358
|
+
* Return the comment of the given artifact or element.
|
|
359
|
+
* Uses the first block (everything up to the first empty line (double \n)).
|
|
360
|
+
* Remove leading/trailing whitespace.
|
|
361
|
+
* Does not escape any characters.
|
|
362
|
+
*
|
|
363
|
+
* @param {CSN.Artifact|CSN.Element} obj
|
|
364
|
+
* @returns {string}
|
|
365
|
+
* @todo Warning/info to user?
|
|
366
|
+
*/
|
|
352
367
|
function getHanaComment(obj) {
|
|
353
|
-
return obj.doc.split('\n\n')[0].trim()
|
|
368
|
+
return obj.doc.split('\n\n')[0].trim();
|
|
354
369
|
}
|
|
355
370
|
|
|
356
371
|
/**
|
|
@@ -368,6 +383,120 @@ function getSqlSnippets(options, obj) {
|
|
|
368
383
|
return { front, back };
|
|
369
384
|
}
|
|
370
385
|
|
|
386
|
+
/**
|
|
387
|
+
* A function used to render a certain part of an expression object
|
|
388
|
+
*
|
|
389
|
+
* @callback renderPart
|
|
390
|
+
* @param {object|array} expression
|
|
391
|
+
* @param {CdlRenderEnvironment} env
|
|
392
|
+
* @this {{inline: Boolean, nestedExpr: Boolean}}
|
|
393
|
+
* @returns {string}
|
|
394
|
+
*/
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* The object containing the concrete rendering functions for the different parts
|
|
398
|
+
* of an expression
|
|
399
|
+
*
|
|
400
|
+
* @typedef {object} ExpressionConfiguration
|
|
401
|
+
* @property {(x: any) => string} finalize The final function to call on the expression(-string) before returning
|
|
402
|
+
* @property {renderPart} explicitTypeCast
|
|
403
|
+
* @property {renderPart} val
|
|
404
|
+
* @property {renderPart} enum
|
|
405
|
+
* @property {renderPart} ref
|
|
406
|
+
* @property {renderPart} aliasOnly
|
|
407
|
+
* @property {renderPart} windowFunction
|
|
408
|
+
* @property {renderPart} func
|
|
409
|
+
* @property {renderPart} xpr
|
|
410
|
+
* @property {renderPart} SELECT
|
|
411
|
+
* @property {renderPart} SET
|
|
412
|
+
* @property {boolean} [inline]
|
|
413
|
+
* @property {boolean} [nestedExpr]
|
|
414
|
+
*/
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* Render an expression (including paths and values) or condition 'x'.
|
|
418
|
+
* (no trailing LF, don't indent if inline)
|
|
419
|
+
*
|
|
420
|
+
* @param {ExpressionConfiguration} renderer
|
|
421
|
+
* @returns {Function} Rendered expression
|
|
422
|
+
*/
|
|
423
|
+
function getExpressionRenderer(renderer) {
|
|
424
|
+
/**
|
|
425
|
+
* Render an expression (including paths and values) or condition 'x'.
|
|
426
|
+
* (no trailing LF, don't indent if inline)
|
|
427
|
+
*
|
|
428
|
+
* @todo Reuse this with toCdl
|
|
429
|
+
* @param {Array|object|string} expr Expression to render
|
|
430
|
+
* @param {object} env Render environment
|
|
431
|
+
* @param {boolean} [inline=true] Whether to render the expression inline
|
|
432
|
+
* @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
|
|
433
|
+
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
|
|
434
|
+
* Note: This is a hack for casts() inside groupBy.
|
|
435
|
+
* @returns {string} Rendered expression
|
|
436
|
+
*/
|
|
437
|
+
return function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
|
|
438
|
+
// Compound expression
|
|
439
|
+
if (Array.isArray(expr)) {
|
|
440
|
+
const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
|
|
441
|
+
return beautifyExprArray(tokens);
|
|
442
|
+
}
|
|
443
|
+
else if (typeof expr === 'object' && expr !== null) {
|
|
444
|
+
if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type && !expr.cast.target)
|
|
445
|
+
return renderer.explicitTypeCast.call({ inline, nestedExpr }, expr, env);
|
|
446
|
+
return renderExprObject(expr);
|
|
447
|
+
}
|
|
448
|
+
// Not a literal value but part of an operator, function etc - just leave as it is
|
|
449
|
+
// FIXME: For the sake of simplicity, we should get away from all this uppercasing in toSql
|
|
450
|
+
return renderer.finalize.call({ inline, nestedExpr }, expr, env);
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Various special cases represented as objects
|
|
455
|
+
*
|
|
456
|
+
* @param {object} x Expression
|
|
457
|
+
* @returns {string} String representation of the expression
|
|
458
|
+
*/
|
|
459
|
+
function renderExprObject(x) {
|
|
460
|
+
if (x.list) { // TODO: Does this still exist?
|
|
461
|
+
return `(${x.list.map(item => renderExpr(item, env, inline, false)).join(', ')})`;
|
|
462
|
+
}
|
|
463
|
+
else if (x.val !== undefined) {
|
|
464
|
+
return renderer.val.call({ inline, nestedExpr }, x, env);
|
|
465
|
+
}
|
|
466
|
+
// Enum symbol
|
|
467
|
+
else if (x['#']) {
|
|
468
|
+
return renderer.enum.call({ inline, nestedExpr }, x, env);
|
|
469
|
+
}
|
|
470
|
+
// Reference: Array of path steps, possibly preceded by ':'
|
|
471
|
+
else if (x.ref) {
|
|
472
|
+
return renderer.ref.call({ inline, nestedExpr }, x, env);
|
|
473
|
+
}
|
|
474
|
+
// Function call, possibly with args (use '=>' for named args)
|
|
475
|
+
else if (x.func) {
|
|
476
|
+
if (x.xpr)
|
|
477
|
+
return renderer.windowFunction.call({ inline, nestedExpr }, x, env);
|
|
478
|
+
return renderer.func.call({ inline, nestedExpr }, x, env);
|
|
479
|
+
}
|
|
480
|
+
// Nested expression
|
|
481
|
+
else if (x.xpr) {
|
|
482
|
+
return renderer.xpr.call({ inline, nestedExpr }, x, env);
|
|
483
|
+
}
|
|
484
|
+
// Sub-select
|
|
485
|
+
else if (x.SELECT) {
|
|
486
|
+
return renderer.SELECT.call({ inline, nestedExpr }, x, env);
|
|
487
|
+
}
|
|
488
|
+
else if (x.SET) {
|
|
489
|
+
return renderer.SET.call({ inline, nestedExpr }, x, env);
|
|
490
|
+
}
|
|
491
|
+
else if (x.as && x.cast && x.cast.type && x.cast.target) {
|
|
492
|
+
return renderer.aliasOnly.call({ inline, nestedExpr }, x, env);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
371
500
|
/**
|
|
372
501
|
* @typedef CdlRenderEnvironment Rendering environment used throughout the render process.
|
|
373
502
|
*
|
|
@@ -386,6 +515,7 @@ function getSqlSnippets(options, obj) {
|
|
|
386
515
|
|
|
387
516
|
module.exports = {
|
|
388
517
|
renderFunc,
|
|
518
|
+
getExpressionRenderer,
|
|
389
519
|
beautifyExprArray,
|
|
390
520
|
getNamespace,
|
|
391
521
|
getRealName,
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -40,18 +40,18 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
const renderAsHdbconstraint = options.transformation === 'hdbcds' ||
|
|
43
|
-
|
|
43
|
+
options.src === 'hdi' ||
|
|
44
44
|
(options.manageConstraints && options.manageConstraints.src === 'hdi');
|
|
45
45
|
|
|
46
|
-
const {
|
|
47
|
-
const forSqlite = options.
|
|
46
|
+
const { sqlMapping } = options;
|
|
47
|
+
const forSqlite = options.sqlDialect === 'sqlite';
|
|
48
48
|
let result = '';
|
|
49
49
|
result += `${indent}CONSTRAINT ${quoteId(constraint.identifier)}\n`;
|
|
50
50
|
if (renderAsHdbconstraint)
|
|
51
|
-
result += `${indent}ON ${quoteId(getResultingName(csn,
|
|
51
|
+
result += `${indent}ON ${quoteId(getResultingName(csn, sqlMapping, constraint.dependentTable))}\n`;
|
|
52
52
|
if (!alterConstraint) {
|
|
53
53
|
result += `${indent}FOREIGN KEY(${constraint.foreignKey.map(quoteId).join(', ')})\n`;
|
|
54
|
-
result += `${indent}REFERENCES ${quoteId(getResultingName(csn,
|
|
54
|
+
result += `${indent}REFERENCES ${quoteId(getResultingName(csn, sqlMapping, constraint.parentTable))}(${constraint.parentKey.map(quoteId).join(', ')})\n`;
|
|
55
55
|
const onDeleteRemark = constraint.onDeleteRemark ? ` -- ${constraint.onDeleteRemark}` : '';
|
|
56
56
|
|
|
57
57
|
// omit 'RESTRICT' action for ON UPDATE / ON DELETE, because it interferes with deferred constraint check
|
|
@@ -65,12 +65,13 @@ function renderReferentialConstraint(constraint, indent, toUpperCase, csn, optio
|
|
|
65
65
|
}
|
|
66
66
|
}
|
|
67
67
|
// constraint enforcement / validation must be switched off using sqlite pragma statement
|
|
68
|
-
|
|
68
|
+
// Does not include HDBCDS.
|
|
69
|
+
if (options.toSql && !forSqlite) {
|
|
69
70
|
result += `${indent}${!constraint.validated ? 'NOT ' : ''}VALIDATED\n`;
|
|
70
71
|
result += `${indent}${!constraint.enforced ? 'NOT ' : ''}ENFORCED\n`;
|
|
71
72
|
}
|
|
72
73
|
// for sqlite, the DEFERRABLE keyword is required
|
|
73
|
-
result += `${indent}${
|
|
74
|
+
result += `${indent}${forSqlite ? 'DEFERRABLE ' : ''}INITIALLY DEFERRED`;
|
|
74
75
|
return result;
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -85,9 +86,9 @@ function getIdentifierUtils(options) {
|
|
|
85
86
|
/**
|
|
86
87
|
* Return 'name' with appropriate "-quotes.
|
|
87
88
|
* Additionally perform the following conversions on 'name'
|
|
88
|
-
* If 'options.
|
|
89
|
+
* If 'options.sqlMapping' is 'plain'
|
|
89
90
|
* - replace '.' or '::' by '_'
|
|
90
|
-
* else if 'options.
|
|
91
|
+
* else if 'options.sqlMapping' is 'quoted'
|
|
91
92
|
* - replace '::' by '.'
|
|
92
93
|
* Complain about names that collide with known SQL keywords or functions
|
|
93
94
|
*
|
|
@@ -97,13 +98,13 @@ function getIdentifierUtils(options) {
|
|
|
97
98
|
function quoteSqlId(name) {
|
|
98
99
|
name = prepareIdentifier(name);
|
|
99
100
|
|
|
100
|
-
switch (options.
|
|
101
|
+
switch (options.sqlMapping) {
|
|
101
102
|
case 'plain':
|
|
102
|
-
return smartId(name, options.
|
|
103
|
+
return smartId(name, options.sqlDialect);
|
|
103
104
|
case 'quoted':
|
|
104
|
-
return delimitedId(name, options.
|
|
105
|
+
return delimitedId(name, options.sqlDialect);
|
|
105
106
|
case 'hdbcds':
|
|
106
|
-
return delimitedId(name, options.
|
|
107
|
+
return delimitedId(name, options.sqlDialect);
|
|
107
108
|
default:
|
|
108
109
|
return undefined;
|
|
109
110
|
}
|
|
@@ -111,9 +112,9 @@ function getIdentifierUtils(options) {
|
|
|
111
112
|
|
|
112
113
|
/**
|
|
113
114
|
* Prepare an identifier:
|
|
114
|
-
* If 'options.
|
|
115
|
+
* If 'options.sqlMapping' is 'plain'
|
|
115
116
|
* - replace '.' or '::' by '_'
|
|
116
|
-
* else if 'options.
|
|
117
|
+
* else if 'options.sqlMapping' is 'quoted'
|
|
117
118
|
* - replace '::' by '.'
|
|
118
119
|
*
|
|
119
120
|
* @param {string} name Identifier to prepare
|
|
@@ -121,11 +122,11 @@ function getIdentifierUtils(options) {
|
|
|
121
122
|
*/
|
|
122
123
|
function prepareIdentifier(name) {
|
|
123
124
|
// Sanity check
|
|
124
|
-
if (options.
|
|
125
|
-
throw new ModelError(`Not expecting ${options.
|
|
125
|
+
if (options.sqlDialect === 'sqlite' && options.sqlMapping !== 'plain')
|
|
126
|
+
throw new ModelError(`Not expecting ${options.sqlMapping} names for 'sqlite' dialect`);
|
|
126
127
|
|
|
127
128
|
|
|
128
|
-
switch (options.
|
|
129
|
+
switch (options.sqlMapping) {
|
|
129
130
|
case 'plain':
|
|
130
131
|
return name.replace(/(\.|::)/g, '_');
|
|
131
132
|
case 'quoted':
|
|
@@ -133,7 +134,7 @@ function getIdentifierUtils(options) {
|
|
|
133
134
|
case 'hdbcds':
|
|
134
135
|
return name;
|
|
135
136
|
default:
|
|
136
|
-
throw new ModelError(`No matching rendering found for naming mode ${options.
|
|
137
|
+
throw new ModelError(`No matching rendering found for naming mode ${options.sqlMapping}`);
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
}
|
package/lib/sql-identifier.js
CHANGED
|
@@ -45,6 +45,12 @@ const sqlDialects = {
|
|
|
45
45
|
effectiveName: name => name,
|
|
46
46
|
asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
|
|
47
47
|
},
|
|
48
|
+
postgres: {
|
|
49
|
+
regularRegex: /^[A-Za-z_][A-Za-z_$0-9]*$/,
|
|
50
|
+
reservedWords: keywords.postgres,
|
|
51
|
+
effectiveName: name => name.toLowerCase(),
|
|
52
|
+
asDelimitedId: name => `"${ name.replace(/"/g, '""')}"`,
|
|
53
|
+
},
|
|
48
54
|
hana: {
|
|
49
55
|
regularRegex: /^[A-Za-z_][A-Za-z_$#0-9]*$/,
|
|
50
56
|
reservedWords: keywords.hana,
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"prefer-template": "error",
|
|
9
9
|
"no-trailing-spaces": "error",
|
|
10
10
|
"template-curly-spacing":["error", "never"],
|
|
11
|
-
"complexity": ["warn",
|
|
11
|
+
"complexity": ["warn", 40],
|
|
12
12
|
"max-len": "off",
|
|
13
13
|
// Don't enforce stupid descriptions
|
|
14
14
|
"jsdoc/require-param-description": "off",
|
|
@@ -17,15 +17,16 @@
|
|
|
17
17
|
"sonarjs/prefer-single-boolean-return": "off",
|
|
18
18
|
// Very whiny and nitpicky
|
|
19
19
|
"sonarjs/cognitive-complexity": "off",
|
|
20
|
+
"sonarjs/no-duplicate-string": "off",
|
|
20
21
|
// Does not recognize TS types
|
|
21
22
|
"jsdoc/no-undefined-types": "off"
|
|
22
23
|
},
|
|
23
24
|
"parserOptions": {
|
|
24
|
-
"ecmaVersion":
|
|
25
|
+
"ecmaVersion": 2020,
|
|
25
26
|
"sourceType": "script"
|
|
26
27
|
},
|
|
27
28
|
"env": {
|
|
28
|
-
"
|
|
29
|
+
"es2020": true,
|
|
29
30
|
"node": true
|
|
30
31
|
},
|
|
31
32
|
"settings": {
|
|
@@ -37,7 +37,7 @@ function attachOnConditions(csn, pathDelimiter) {
|
|
|
37
37
|
/**
|
|
38
38
|
* Create the foreign key elements for a managed association and build the on-condition
|
|
39
39
|
*
|
|
40
|
-
* @param {
|
|
40
|
+
* @param {object} elem The association to process
|
|
41
41
|
* @param {string} elemName
|
|
42
42
|
* @returns {void}
|
|
43
43
|
*/
|
|
@@ -167,7 +167,7 @@ function getManagedAssocStepsInOnConditionFinalizer(csn, pathDelimiter) {
|
|
|
167
167
|
*
|
|
168
168
|
* @param {Array} links
|
|
169
169
|
* @param {number} startIndex
|
|
170
|
-
* @returns {
|
|
170
|
+
* @returns {object | undefined} CSN definition of the source of the managed association
|
|
171
171
|
*/
|
|
172
172
|
function findSource(links, startIndex) {
|
|
173
173
|
for (let i = startIndex; i >= 0; i--) {
|
|
@@ -81,26 +81,16 @@ function getAssocToSkippedIgnorer(csn, options, messageFunctions) {
|
|
|
81
81
|
* @todo Why do we check for @cds.persistence.exists here if the parent-function only calls this for skip/abstract?
|
|
82
82
|
*/
|
|
83
83
|
function ignore(member, memberName, prop, path) {
|
|
84
|
-
if (options.sqlDialect === 'hana' &&
|
|
85
|
-
|
|
84
|
+
if (options.sqlDialect === 'hana' &&
|
|
85
|
+
!member._ignore && member.target &&
|
|
86
|
+
isAssocOrComposition(member.type) &&
|
|
87
|
+
!isPersistedOnDatabase(csn.definitions[member.target])) {
|
|
86
88
|
info(null, path,
|
|
87
|
-
{ target: member.target, anno:
|
|
89
|
+
{ target: member.target, anno: '@cds.persistence.skip' },
|
|
88
90
|
'Association has been removed as it\'s target $(TARGET) is annotated with $(ANNO)');
|
|
89
91
|
member._ignore = true;
|
|
90
92
|
}
|
|
91
93
|
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Check whether the given artifact is an unreachable association target because it will not "realy" hit the database:
|
|
95
|
-
* - @cds.persistence.skip/exists
|
|
96
|
-
* - abstract
|
|
97
|
-
*
|
|
98
|
-
* @param {CSN.Artifact} art
|
|
99
|
-
* @returns {boolean}
|
|
100
|
-
*/
|
|
101
|
-
function isUnreachableAssociationTarget(art) {
|
|
102
|
-
return !isPersistedOnDatabase(art) || hasAnnotationValue(art, exists);
|
|
103
|
-
}
|
|
104
94
|
}
|
|
105
95
|
|
|
106
96
|
/**
|
|
@@ -277,7 +277,9 @@ function createReferentialConstraints(csn, options) {
|
|
|
277
277
|
// no constraint if either dependent or parent is not persisted
|
|
278
278
|
if (
|
|
279
279
|
hasAnnotationValue(parent, '@cds.persistence.skip') ||
|
|
280
|
-
hasAnnotationValue(dependent, '@cds.persistence.skip')
|
|
280
|
+
hasAnnotationValue(dependent, '@cds.persistence.skip') ||
|
|
281
|
+
hasAnnotationValue(parent, '@cds.persistence.exists') ||
|
|
282
|
+
hasAnnotationValue(dependent, '@cds.persistence.exists')
|
|
281
283
|
)
|
|
282
284
|
return true;
|
|
283
285
|
|
|
@@ -515,7 +517,7 @@ function createReferentialConstraints(csn, options) {
|
|
|
515
517
|
onDeleteRemark = `Up_ link for Composition "${$foreignKeyConstraint.upLinkFor}" implies existential dependency`;
|
|
516
518
|
referentialConstraints[`${getResultingName(csn, 'quoted', artifactName)}_${$foreignKeyConstraint.sourceAssociation}`] = {
|
|
517
519
|
// constraint identifier start with `c__` to avoid name clashes
|
|
518
|
-
identifier: `c__${getResultingName(csn, options.
|
|
520
|
+
identifier: `c__${getResultingName(csn, options.sqlMapping, artifactName)}_${$foreignKeyConstraint.sourceAssociation}`,
|
|
519
521
|
foreignKey: dependentKey,
|
|
520
522
|
parentKey,
|
|
521
523
|
dependentTable: artifactName,
|
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
} = require('../../model/csnUtils');
|
|
9
9
|
const { csnRefs, implicitAs } = require('../../model/csnRefs');
|
|
10
10
|
const { setProp, isBetaEnabled } = require('../../base/model');
|
|
11
|
+
const { forEach } = require('../../utils/objectUtils');
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* For keys, columns, groupBy and orderBy, expand structured things.
|
|
@@ -19,14 +20,15 @@ const { setProp, isBetaEnabled } = require('../../base/model');
|
|
|
19
20
|
* @param {object} messageFunctions
|
|
20
21
|
* @param {Function} messageFunctions.error
|
|
21
22
|
* @param {Function} messageFunctions.info
|
|
22
|
-
* @param {Function} messageFunctions.
|
|
23
|
+
* @param {Function} messageFunctions.throwWithAnyError
|
|
23
24
|
* @param {object} iterateOptions
|
|
24
25
|
*/
|
|
25
|
-
function expandStructureReferences(csn, options, pathDelimiter, { error, info,
|
|
26
|
+
function expandStructureReferences(csn, options, pathDelimiter, { error, info, throwWithAnyError }, iterateOptions = {}) {
|
|
27
|
+
const csnUtils = getUtils(csn);
|
|
26
28
|
const {
|
|
27
|
-
isStructured, get$combined,
|
|
28
|
-
} =
|
|
29
|
-
let { effectiveType, inspectRef } =
|
|
29
|
+
isStructured, get$combined, getFinalBaseTypeWithProps, getServiceName,
|
|
30
|
+
} = csnUtils;
|
|
31
|
+
let { effectiveType, inspectRef } = csnUtils;
|
|
30
32
|
|
|
31
33
|
if (isBetaEnabled(options, 'nestedProjections'))
|
|
32
34
|
rewriteExpandInline();
|
|
@@ -182,7 +184,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
182
184
|
}
|
|
183
185
|
|
|
184
186
|
// We would be broken if we continue with assoc usage to now skipped
|
|
185
|
-
|
|
187
|
+
throwWithAnyError();
|
|
186
188
|
|
|
187
189
|
|
|
188
190
|
for (const {
|
|
@@ -211,7 +213,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
211
213
|
while (stack.length > 0) {
|
|
212
214
|
const [ a, n ] = stack.pop();
|
|
213
215
|
if (a[_dependents]) {
|
|
214
|
-
|
|
216
|
+
forEach(a[_dependents], (dependentName, dependent) => {
|
|
215
217
|
stack.push([ dependent, dependentName ]);
|
|
216
218
|
});
|
|
217
219
|
}
|
|
@@ -238,13 +240,13 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
238
240
|
*/
|
|
239
241
|
function nextBase(parent, base) {
|
|
240
242
|
if (parent.ref) {
|
|
241
|
-
const finalBaseType =
|
|
243
|
+
const finalBaseType = getFinalBaseTypeWithProps(parent._art.type);
|
|
242
244
|
const art = parent._art;
|
|
243
245
|
|
|
244
|
-
if (finalBaseType === 'cds.Association' || finalBaseType === 'cds.Composition')
|
|
246
|
+
if (finalBaseType && (finalBaseType.type === 'cds.Association' || finalBaseType.type === 'cds.Composition'))
|
|
245
247
|
return csn.definitions[art.target].elements;
|
|
246
248
|
|
|
247
|
-
return art.elements || finalBaseType
|
|
249
|
+
return art.elements || finalBaseType?.elements;
|
|
248
250
|
}
|
|
249
251
|
|
|
250
252
|
return base;
|
|
@@ -253,7 +255,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
253
255
|
/**
|
|
254
256
|
* Rewrite expand and inline to "normal" refs
|
|
255
257
|
*
|
|
256
|
-
* @param {CSN.Artifact} root All elements visible
|
|
258
|
+
* @param {CSN.Artifact} root All elements visible from the query source ($combined)
|
|
257
259
|
* @param {CSN.Column[]} columns
|
|
258
260
|
* @param {string[]} excluding
|
|
259
261
|
* @returns {{columns: Array, toMany: Array}} Object with rewritten columns (.expand/.inline) and with any .expand + to-many
|
|
@@ -261,8 +263,12 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
261
263
|
function rewrite(root, columns, excluding) {
|
|
262
264
|
const allToMany = [];
|
|
263
265
|
const newThing = [];
|
|
264
|
-
|
|
265
|
-
|
|
266
|
+
const containsExpandInline = columns.some(col => col.expand || col.inline);
|
|
267
|
+
if (containsExpandInline) // Replace stars - needs to happen before resolving .expand/.inline since the .expand/.inline first path step affects the root *
|
|
268
|
+
columns = replaceStar(root, columns, excluding);
|
|
269
|
+
else
|
|
270
|
+
return { columns, toMany: [] };
|
|
271
|
+
|
|
266
272
|
for (const col of columns) {
|
|
267
273
|
if (col.expand) {
|
|
268
274
|
// TODO: Can col.ref be empty without an as? Assumption is it cannot - if it has, it's an error, we throw, compiler checks.
|
|
@@ -402,8 +408,8 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
402
408
|
* @returns {string|null} Name of any entity
|
|
403
409
|
*/
|
|
404
410
|
function findAnEntity() {
|
|
405
|
-
for (const
|
|
406
|
-
if (
|
|
411
|
+
for (const name in csn.definitions) {
|
|
412
|
+
if (Object.hasOwnProperty.call(csn.definitions, name) && csn.definitions[name].kind === 'entity' && !csn.definitions[name].query)
|
|
407
413
|
return name;
|
|
408
414
|
}
|
|
409
415
|
return null;
|
|
@@ -544,7 +550,7 @@ function expandStructureReferences(csn, options, pathDelimiter, { error, info, t
|
|
|
544
550
|
/**
|
|
545
551
|
* Replace the star and correctly put shadowed things in the right place.
|
|
546
552
|
*
|
|
547
|
-
* @param {
|
|
553
|
+
* @param {object} base The raw set of things a * can expand to
|
|
548
554
|
* @param {Array} subs Things - the .expand/.inline or .columns
|
|
549
555
|
* @param {string[]} [excluding=[]]
|
|
550
556
|
* @returns {Array} If there was a star, expand it and handle shadowing/excluding, else just return subs
|