@sap/cds-compiler 2.15.8 → 3.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 +102 -1590
- package/bin/.eslintrc.json +2 -1
- package/bin/cdsc.js +61 -46
- package/doc/API.md +11 -0
- package/doc/CHANGELOG_ARCHIVE.md +1592 -0
- package/doc/CHANGELOG_BETA.md +26 -5
- package/doc/CHANGELOG_DEPRECATED.md +55 -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 +282 -156
- package/lib/api/options.js +17 -88
- package/lib/api/validate.js +6 -10
- package/lib/base/keywords.js +280 -110
- package/lib/base/message-registry.js +85 -25
- package/lib/base/messages.js +119 -89
- package/lib/base/model.js +46 -2
- package/lib/base/optionProcessorHelper.js +53 -21
- package/lib/checks/actionsFunctions.js +15 -12
- package/lib/checks/annotationsOData.js +1 -1
- 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 +101 -15
- package/lib/checks/types.js +7 -8
- package/lib/checks/utils.js +2 -2
- package/lib/checks/validator.js +3 -3
- package/lib/compiler/assert-consistency.js +78 -21
- package/lib/compiler/base.js +6 -4
- package/lib/compiler/builtins.js +177 -10
- package/lib/compiler/checks.js +1 -1
- package/lib/compiler/define.js +28 -23
- package/lib/compiler/extend.js +75 -18
- package/lib/compiler/finalize-parse-cdl.js +25 -18
- package/lib/compiler/index.js +27 -11
- package/lib/compiler/moduleLayers.js +7 -0
- package/lib/compiler/populate.js +26 -39
- package/lib/compiler/propagator.js +12 -7
- package/lib/compiler/resolve.js +207 -236
- package/lib/compiler/shared.js +100 -93
- package/lib/compiler/tweak-assocs.js +13 -20
- package/lib/compiler/utils.js +20 -6
- package/lib/edm/annotations/preprocessAnnotations.js +12 -13
- package/lib/edm/csn2edm.js +35 -37
- package/lib/edm/edm.js +22 -13
- package/lib/edm/edmAnnoPreprocessor.js +349 -0
- package/lib/edm/edmInboundChecks.js +85 -0
- package/lib/edm/edmPreprocessor.js +338 -689
- package/lib/edm/edmUtils.js +97 -67
- package/lib/gen/Dictionary.json +29 -9
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +8 -31
- package/lib/gen/language.tokens +105 -114
- package/lib/gen/languageLexer.interp +1 -34
- package/lib/gen/languageLexer.js +892 -1007
- package/lib/gen/languageLexer.tokens +95 -106
- package/lib/gen/languageParser.js +20629 -22474
- package/lib/inspect/.eslintrc.json +4 -0
- package/lib/inspect/index.js +14 -0
- package/lib/inspect/inspectModelStatistics.js +81 -0
- package/lib/inspect/inspectPropagation.js +189 -0
- package/lib/inspect/inspectUtils.js +44 -0
- package/lib/json/from-csn.js +74 -69
- package/lib/json/to-csn.js +17 -14
- package/lib/language/antlrParser.js +2 -2
- package/lib/language/docCommentParser.js +61 -38
- package/lib/language/errorStrategy.js +52 -40
- package/lib/language/genericAntlrParser.js +424 -292
- package/lib/language/language.g4 +604 -687
- package/lib/language/multiLineStringParser.js +14 -42
- package/lib/language/textUtils.js +44 -0
- package/lib/main.d.ts +28 -42
- package/lib/main.js +104 -81
- package/lib/model/api.js +1 -1
- package/lib/model/csnRefs.js +57 -30
- package/lib/model/csnUtils.js +189 -287
- package/lib/model/revealInternalProperties.js +32 -10
- package/lib/model/sortViews.js +32 -31
- package/lib/modelCompare/compare.js +3 -0
- package/lib/optionProcessor.js +91 -57
- 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 +387 -367
- package/lib/render/toHdbcds.js +20 -16
- package/lib/render/toRename.js +44 -22
- package/lib/render/toSql.js +81 -59
- package/lib/render/utils/common.js +16 -3
- package/lib/render/utils/sql.js +20 -19
- package/lib/sql-identifier.js +6 -0
- package/lib/transform/db/.eslintrc.json +3 -2
- package/lib/transform/db/associations.js +43 -35
- package/lib/transform/db/cdsPersistence.js +5 -16
- package/lib/transform/db/constraints.js +1 -1
- package/lib/transform/db/expansion.js +7 -6
- package/lib/transform/db/flattening.js +16 -18
- package/lib/transform/db/transformExists.js +7 -5
- package/lib/transform/db/views.js +3 -3
- 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 +30 -24
- package/lib/transform/forOdataNew.js +14 -16
- package/lib/transform/localized.js +35 -25
- package/lib/transform/odata/toFinalBaseType.js +10 -10
- package/lib/transform/odata/typesExposure.js +17 -8
- package/lib/transform/odata/utils.js +1 -38
- package/lib/transform/transformUtilsNew.js +63 -77
- package/lib/transform/translateAssocsToJoins.js +2 -2
- package/lib/transform/universalCsn/.eslintrc.json +2 -2
- package/lib/transform/universalCsn/coreComputed.js +11 -6
- package/lib/transform/universalCsn/universalCsnEnricher.js +33 -5
- package/lib/utils/file.js +31 -21
- package/lib/utils/moduleResolve.js +0 -1
- package/lib/utils/timetrace.js +20 -21
- package/package.json +34 -4
- package/share/messages/syntax-expected-integer.md +9 -8
- 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/checks/unknownMagic.js +0 -41
- package/lib/fix_antlr4-8_warning.js +0 -56
package/lib/render/toHdbcds.js
CHANGED
|
@@ -35,6 +35,16 @@ function getEscapedHanaComment(obj) {
|
|
|
35
35
|
return getHanaComment(obj).replace(/\n/g, '\\n').replace(/'/g, "''");
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Render a string for HDBCDS, i.e. put it in quotes and escape single quotes.
|
|
40
|
+
*
|
|
41
|
+
* @param {string} str
|
|
42
|
+
* @returns {string}
|
|
43
|
+
*/
|
|
44
|
+
function renderStringForHdbcds(str) {
|
|
45
|
+
return `'${str.replace(/'/g, '\'\'')}'`;
|
|
46
|
+
}
|
|
47
|
+
|
|
38
48
|
/**
|
|
39
49
|
* Render the CSN model 'model' to CDS source text. One source is created per
|
|
40
50
|
* top-level artifact. Return a dictionary of top-level artifacts
|
|
@@ -731,7 +741,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
731
741
|
|
|
732
742
|
// Render 'null as <alias>' only for database and if element is virtual
|
|
733
743
|
if (element && element.virtual) {
|
|
734
|
-
if (isDeprecatedEnabled(options, '
|
|
744
|
+
if (isDeprecatedEnabled(options, '_renderVirtualElements'))
|
|
735
745
|
return `${result}${env.indent}null as ${formatIdentifier(leaf)}`;
|
|
736
746
|
}
|
|
737
747
|
else {
|
|
@@ -829,7 +839,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
829
839
|
* @param {object} [elements] For leading query, the elements of the artifact
|
|
830
840
|
* @returns {string} The rendered query
|
|
831
841
|
*/
|
|
832
|
-
function renderQuery(query, isLeadingQuery, env, path = [], elements) {
|
|
842
|
+
function renderQuery(query, isLeadingQuery, env, path = [], elements = null) {
|
|
833
843
|
let result = '';
|
|
834
844
|
env.skipKeys = !isLeadingQuery;
|
|
835
845
|
// Set operator, like UNION, INTERSECT, ...
|
|
@@ -1223,7 +1233,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1223
1233
|
case 'timestamp':
|
|
1224
1234
|
return `${x.literal}'${x.val}'`;
|
|
1225
1235
|
case 'string':
|
|
1226
|
-
return
|
|
1236
|
+
return renderStringForHdbcds(x.val);
|
|
1227
1237
|
case 'object':
|
|
1228
1238
|
if (x.val === null)
|
|
1229
1239
|
return 'null';
|
|
@@ -1260,22 +1270,16 @@ function toHdbcdsSource(csn, options) {
|
|
|
1260
1270
|
const magicReplacement = getVariableReplacement(x.ref, options);
|
|
1261
1271
|
if (x.ref[0] === '$user') {
|
|
1262
1272
|
if (magicReplacement !== null)
|
|
1263
|
-
return
|
|
1264
|
-
|
|
1265
|
-
// Keep old way of solving this to remain backwards compatible
|
|
1266
|
-
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1267
|
-
if (x.ref[1] === 'id') {
|
|
1268
|
-
if (options.magicVars && options.magicVars.user && (typeof options.magicVars.user === 'string' || options.magicVars.user instanceof String))
|
|
1269
|
-
return `'${options.magicVars.user}'`;
|
|
1273
|
+
return renderStringForHdbcds(magicReplacement);
|
|
1270
1274
|
|
|
1271
|
-
|
|
1272
|
-
return `'${options.magicVars.user.id}'`;
|
|
1275
|
+
// Note: The compiler already transforms $user into $user.id.
|
|
1273
1276
|
|
|
1277
|
+
// FIXME: this is all not enough: we might need an explicit select item alias (?)
|
|
1278
|
+
if (x.ref[1] === 'id')
|
|
1274
1279
|
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1275
|
-
|
|
1276
|
-
else if (x.ref[1] === 'locale')
|
|
1280
|
+
|
|
1281
|
+
else if (x.ref[1] === 'locale')
|
|
1277
1282
|
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1278
|
-
}
|
|
1279
1283
|
}
|
|
1280
1284
|
else if (x.ref[0] === '$at') {
|
|
1281
1285
|
if (x.ref[1] === 'from')
|
|
@@ -1285,7 +1289,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1285
1289
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1286
1290
|
}
|
|
1287
1291
|
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1288
|
-
return
|
|
1292
|
+
return renderStringForHdbcds(magicReplacement);
|
|
1289
1293
|
}
|
|
1290
1294
|
}
|
|
1291
1295
|
return `${(x.param || x.global) ? ':' : ''}${x.ref.map((step, index) => renderPathStep(step, index, x.ref, env, this.inline)).join('.')}`;
|
package/lib/render/toRename.js
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const {
|
|
4
|
+
const { makeMessageFunction } = require('../base/messages');
|
|
5
5
|
const { checkCSNVersion } = require('../json/csnVersion');
|
|
6
|
-
const { getUtils } = require('../model/csnUtils');
|
|
6
|
+
const { getUtils, forEachDefinition } = require('../model/csnUtils');
|
|
7
|
+
const { optionProcessor } = require('../optionProcessor');
|
|
8
|
+
const { isBetaEnabled } = require('../base/model');
|
|
9
|
+
const { transformForHanaWithCsn } = require('../transform/forHanaNew');
|
|
7
10
|
|
|
8
|
-
// FIXME: This is not up-to-date in regards to the changes to hdbcds/sql quoting etc.
|
|
9
11
|
|
|
10
12
|
/**
|
|
13
|
+
* FIXME: Not yet supported, only in beta mode
|
|
14
|
+
* FIXME: This is not up-to-date in regards to the changes to hdbcds/sql quoting etc.
|
|
11
15
|
*
|
|
12
|
-
*
|
|
16
|
+
* Generate SQL DDL rename statements for a migration, renaming existing tables and their
|
|
13
17
|
* columns so that they match the result of "toHana" or "toSql" with the 'plain' option for names.
|
|
14
18
|
* Expects the naming convention of the existing tables to be either 'quoted' or 'hdbcds' (default).
|
|
15
|
-
* The following options control what is actually generated:
|
|
19
|
+
* The following options control what is actually generated (see help above):
|
|
16
20
|
* options : {
|
|
17
|
-
*
|
|
21
|
+
* sqlMapping : existing names, either 'quoted' or 'hdbcds' (default)
|
|
18
22
|
* }
|
|
19
23
|
* Return a dictionary of top-level artifacts by their names, like this:
|
|
20
24
|
* { "foo" : "RENAME TABLE \"foo\" ...",
|
|
@@ -22,34 +26,52 @@ const { getUtils } = require('../model/csnUtils');
|
|
|
22
26
|
* }
|
|
23
27
|
*
|
|
24
28
|
* @todo clarify input parameters
|
|
25
|
-
* @param {CSN.Model}
|
|
29
|
+
* @param {CSN.Model} inputCsn CSN?
|
|
26
30
|
* @param {CSN.Options} options Transformation options
|
|
27
31
|
* @returns {object} A dictionary of name: rename statement
|
|
28
32
|
*/
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
33
|
+
function toRename(inputCsn, options) {
|
|
34
|
+
const { error, warning, throwWithError } = makeMessageFunction(inputCsn, options, 'to.rename');
|
|
35
|
+
|
|
36
|
+
// Merge options with defaults.
|
|
37
|
+
options = Object.assign({ sqlMapping: 'hdbcds' }, options);
|
|
38
|
+
|
|
39
|
+
// Verify options
|
|
40
|
+
optionProcessor.verifyOptions(options, 'toRename').forEach(complaint => warning(null, null, `${complaint}`));
|
|
41
|
+
checkCSNVersion(inputCsn, options);
|
|
42
|
+
|
|
43
|
+
// Requires beta mode
|
|
44
|
+
if (!isBetaEnabled(options, 'toRename'))
|
|
45
|
+
error(null, null, 'Generation of SQL rename statements is not supported yet (only in beta mode)');
|
|
32
46
|
|
|
33
|
-
|
|
47
|
+
// FIXME: Currently, 'toRename' implies transformation for HANA (transferring the options to forHana)
|
|
48
|
+
const csn = transformForHanaWithCsn(inputCsn, options, 'to.rename');
|
|
49
|
+
// forHanaCsn looses empty contexts and services, add them again so that toRename can calculate the namespaces
|
|
50
|
+
forEachDefinition(csn, (artifact, artifactName) => {
|
|
51
|
+
if ((artifact.kind === 'context' || artifact.kind === 'service') && csn.definitions[artifactName] === undefined)
|
|
52
|
+
csn.definitions[artifactName] = artifact;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const result = Object.create(null);
|
|
56
|
+
const { getNamespaceOfArtifact } = getUtils(csn, false);
|
|
34
57
|
|
|
35
|
-
const { getNamespaceOfArtifact } = getUtils(csn);
|
|
36
58
|
// Render each artifact on its own
|
|
37
59
|
for (const artifactName in csn.definitions) {
|
|
38
60
|
const sourceStr = renameTableAndColumns(artifactName, csn.definitions[artifactName]);
|
|
39
|
-
|
|
40
61
|
if (sourceStr !== '')
|
|
41
62
|
result[artifactName] = sourceStr;
|
|
42
63
|
}
|
|
43
|
-
// Throw exception in case of errors
|
|
44
|
-
if (hasErrors(options.messages))
|
|
45
|
-
throw new CompilationError(options.messages);
|
|
46
64
|
|
|
47
|
-
|
|
65
|
+
throwWithError();
|
|
48
66
|
|
|
67
|
+
return {
|
|
68
|
+
rename: result,
|
|
69
|
+
options,
|
|
70
|
+
};
|
|
49
71
|
|
|
50
72
|
/**
|
|
51
73
|
* If 'art' is a non-view entity, generate SQL statements to rename the corresponding
|
|
52
|
-
* table and its columns from the naming conventions given in 'options.
|
|
74
|
+
* table and its columns from the naming conventions given in 'options.sqlMapping'
|
|
53
75
|
* (either 'quoted' or 'hdbcds') to 'plain'. In addition, drop any existing associations
|
|
54
76
|
* from the columns (they would likely become invalid anyway).
|
|
55
77
|
* Do not rename anything if the names are identical.
|
|
@@ -97,7 +119,7 @@ function toRenameDdl(csn, options) {
|
|
|
97
119
|
* @returns {string} Absolute name
|
|
98
120
|
*/
|
|
99
121
|
function absoluteCdsName(name) {
|
|
100
|
-
if (options.
|
|
122
|
+
if (options.sqlMapping !== 'hdbcds')
|
|
101
123
|
return name;
|
|
102
124
|
|
|
103
125
|
const namespaceName = getNamespaceOfArtifact(name);
|
|
@@ -108,14 +130,14 @@ function toRenameDdl(csn, options) {
|
|
|
108
130
|
}
|
|
109
131
|
|
|
110
132
|
/**
|
|
111
|
-
* Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.
|
|
133
|
+
* Return 'name' with appropriate "-quotes, also replacing '::' by '.' if 'options.sqlMapping'
|
|
112
134
|
* is 'quoted'
|
|
113
135
|
*
|
|
114
136
|
* @param {string} name Name to quote
|
|
115
137
|
* @returns {string} Quoted string
|
|
116
138
|
*/
|
|
117
139
|
function quoteSqlId(name) {
|
|
118
|
-
if (options.
|
|
140
|
+
if (options.sqlMapping === 'quoted')
|
|
119
141
|
name = name.replace(/::/g, '.');
|
|
120
142
|
|
|
121
143
|
return `"${name.replace(/"/g, '""')}"`;
|
|
@@ -134,5 +156,5 @@ function toRenameDdl(csn, options) {
|
|
|
134
156
|
}
|
|
135
157
|
|
|
136
158
|
module.exports = {
|
|
137
|
-
|
|
159
|
+
toRename,
|
|
138
160
|
};
|
package/lib/render/toSql.js
CHANGED
|
@@ -97,8 +97,8 @@ function toSqlDdl(csn, options) {
|
|
|
97
97
|
aliasOnly(x, _env) {
|
|
98
98
|
return x.as;
|
|
99
99
|
},
|
|
100
|
-
windowFunction: (x, env) => renderWindowFunction(smartFuncId(prepareIdentifier(x.func), options.
|
|
101
|
-
func: (x, env) => renderFunc(smartFuncId(prepareIdentifier(x.func), options.
|
|
100
|
+
windowFunction: (x, env) => renderWindowFunction(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, env),
|
|
101
|
+
func: (x, env) => renderFunc(smartFuncId(prepareIdentifier(x.func), options.sqlDialect), x, options.sqlDialect, a => renderArgs(a, '=>', env, null)),
|
|
102
102
|
xpr(x, env) {
|
|
103
103
|
if (this.nestedExpr && !x.cast)
|
|
104
104
|
return `(${renderExpr(x.xpr, env, this.inline, true)})`;
|
|
@@ -117,14 +117,18 @@ function toSqlDdl(csn, options) {
|
|
|
117
117
|
*/
|
|
118
118
|
addColumns: {
|
|
119
119
|
fromElementStrings(tableName, eltStrings) {
|
|
120
|
+
if (options.sqlDialect === 'sqlite') // SQLite can only alter one column at a time
|
|
121
|
+
return eltStrings.map(eltString => `ALTER TABLE ${tableName} ADD ${eltString};`);
|
|
122
|
+
|
|
120
123
|
const elts = options.sqlDialect === 'hana' ? `(${eltStrings.join(', ')})` : `${eltStrings.join(', ')}`;
|
|
121
124
|
return [ `ALTER TABLE ${tableName} ADD ${elts};` ];
|
|
122
125
|
},
|
|
123
126
|
fromElementsObj(artifactName, tableName, elementsObj, env, duplicateChecker) {
|
|
124
127
|
// Only extend with 'ADD' for elements/associations
|
|
125
128
|
// TODO: May also include 'RENAME' at a later stage
|
|
129
|
+
const alterEnv = activateAlterMode(env);
|
|
126
130
|
const elements = Object.entries(elementsObj)
|
|
127
|
-
.map(([ name, elt ]) => renderElement(artifactName, name, elt, duplicateChecker, null,
|
|
131
|
+
.map(([ name, elt ]) => renderElement(artifactName, name, elt, duplicateChecker, null, alterEnv))
|
|
128
132
|
.filter(s => s !== '');
|
|
129
133
|
|
|
130
134
|
if (elements.length)
|
|
@@ -138,10 +142,10 @@ function toSqlDdl(csn, options) {
|
|
|
138
142
|
TODO duplicity check
|
|
139
143
|
*/
|
|
140
144
|
addAssociations(artifactName, tableName, elementsObj, env) {
|
|
141
|
-
return Object.entries(elementsObj)
|
|
145
|
+
return options.sqlDialect === 'hana' ? Object.entries(elementsObj)
|
|
142
146
|
.map(([ name, elt ]) => renderAssociationElement(name, elt, env))
|
|
143
147
|
.filter(s => s !== '')
|
|
144
|
-
.map(eltStr => `ALTER TABLE ${tableName} ADD ASSOCIATION (${eltStr});`);
|
|
148
|
+
.map(eltStr => `ALTER TABLE ${tableName} ADD ASSOCIATION (${eltStr});`) : [];
|
|
145
149
|
},
|
|
146
150
|
/*
|
|
147
151
|
Render key addition as HANA SQL.
|
|
@@ -159,7 +163,7 @@ function toSqlDdl(csn, options) {
|
|
|
159
163
|
Render association removals as HANA SQL.
|
|
160
164
|
*/
|
|
161
165
|
dropAssociation(tableName, sqlId) {
|
|
162
|
-
return [ `ALTER TABLE ${tableName} DROP ASSOCIATION ${sqlId};` ];
|
|
166
|
+
return options.sqlDialect === 'hana' ? [ `ALTER TABLE ${tableName} DROP ASSOCIATION ${sqlId};` ] : [];
|
|
163
167
|
},
|
|
164
168
|
/*
|
|
165
169
|
Render primary-key removals as HANA SQL.
|
|
@@ -238,7 +242,7 @@ function toSqlDdl(csn, options) {
|
|
|
238
242
|
};
|
|
239
243
|
|
|
240
244
|
// Registries for artifact and element names per CSN section
|
|
241
|
-
const definitionsDuplicateChecker = new DuplicateChecker(options.
|
|
245
|
+
const definitionsDuplicateChecker = new DuplicateChecker(options.sqlMapping);
|
|
242
246
|
const deletionsDuplicateChecker = new DuplicateChecker();
|
|
243
247
|
const extensionsDuplicateChecker = new DuplicateChecker();
|
|
244
248
|
const removeElementsDuplicateChecker = new DuplicateChecker();
|
|
@@ -262,7 +266,7 @@ function toSqlDdl(csn, options) {
|
|
|
262
266
|
// Render each artifact extension
|
|
263
267
|
// Only HANA SQL is currently supported.
|
|
264
268
|
// Note that extensions may contain new elements referenced in migrations, thus should be compiled first.
|
|
265
|
-
if (csn.extensions && (options.
|
|
269
|
+
if (csn.extensions && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
266
270
|
for (const extension of options && options.testMode ? sortCsn(csn.extensions) : csn.extensions) {
|
|
267
271
|
if (extension.extend) {
|
|
268
272
|
const artifactName = extension.extend;
|
|
@@ -275,7 +279,7 @@ function toSqlDdl(csn, options) {
|
|
|
275
279
|
|
|
276
280
|
// Render each artifact change
|
|
277
281
|
// Only HANA SQL is currently supported.
|
|
278
|
-
if (csn.migrations && (options.
|
|
282
|
+
if (csn.migrations && (options.sqlDialect === 'hana' || isBetaEnabled(options, 'sqlExtensions'))) {
|
|
279
283
|
for (const migration of options && options.testMode ? sortCsn(csn.migrations) : csn.migrations) {
|
|
280
284
|
if (migration.migrate) {
|
|
281
285
|
const artifactName = migration.migrate;
|
|
@@ -294,7 +298,7 @@ function toSqlDdl(csn, options) {
|
|
|
294
298
|
// Throw exception in case of errors
|
|
295
299
|
throwWithAnyError();
|
|
296
300
|
|
|
297
|
-
// Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if
|
|
301
|
+
// Transfer results from hdb-specific dictionaries into 'sql' dictionary in proper order if src === 'sql'
|
|
298
302
|
// (relying on the order of dictionaries above)
|
|
299
303
|
// FIXME: Should consider inter-view dependencies, too
|
|
300
304
|
const sql = Object.create(null);
|
|
@@ -305,10 +309,10 @@ function toSqlDdl(csn, options) {
|
|
|
305
309
|
const { deletions, migrations: _, ...hdbKinds } = mainResultObj;
|
|
306
310
|
for (const hdbKind of Object.keys(hdbKinds)) {
|
|
307
311
|
for (const name in mainResultObj[hdbKind]) {
|
|
308
|
-
if (options.
|
|
312
|
+
if (options.src === 'sql') {
|
|
309
313
|
let sourceString = mainResultObj[hdbKind][name];
|
|
310
314
|
// Hack: Other than in 'hdbtable' files, in HANA SQL COLUMN is not mandatory but default.
|
|
311
|
-
if (options.
|
|
315
|
+
if (options.sqlDialect === 'hana' && hdbKind === 'hdbtable' && sourceString.startsWith('COLUMN '))
|
|
312
316
|
sourceString = sourceString.slice('COLUMN '.length);
|
|
313
317
|
sql[name] = `${options.testMode ? '' : sqlVersionLine}CREATE ${sourceString};`;
|
|
314
318
|
}
|
|
@@ -316,7 +320,7 @@ function toSqlDdl(csn, options) {
|
|
|
316
320
|
mainResultObj[hdbKind][name] = sqlVersionLine + mainResultObj[hdbKind][name];
|
|
317
321
|
}
|
|
318
322
|
}
|
|
319
|
-
if (options.
|
|
323
|
+
if (options.src === 'sql')
|
|
320
324
|
delete mainResultObj[hdbKind];
|
|
321
325
|
}
|
|
322
326
|
|
|
@@ -329,7 +333,7 @@ function toSqlDdl(csn, options) {
|
|
|
329
333
|
mainResultObj.sql = sql;
|
|
330
334
|
}
|
|
331
335
|
|
|
332
|
-
if (options.
|
|
336
|
+
if (options.src === 'sql')
|
|
333
337
|
mainResultObj.sql = sql;
|
|
334
338
|
|
|
335
339
|
for (const name in deletions)
|
|
@@ -420,7 +424,7 @@ function toSqlDdl(csn, options) {
|
|
|
420
424
|
* @returns {string} Artifact name
|
|
421
425
|
*/
|
|
422
426
|
function renderArtifactName(artifactName) {
|
|
423
|
-
return quoteSqlId(getResultingName(csn, options.
|
|
427
|
+
return quoteSqlId(getResultingName(csn, options.sqlMapping, artifactName));
|
|
424
428
|
}
|
|
425
429
|
|
|
426
430
|
// Render an artifact migration into the appropriate dictionary of 'resultObj'.
|
|
@@ -434,7 +438,7 @@ function toSqlDdl(csn, options) {
|
|
|
434
438
|
function getEltStr(defVariant, eltName) {
|
|
435
439
|
return defVariant.target
|
|
436
440
|
? renderAssociationElement(eltName, defVariant, env)
|
|
437
|
-
: renderElement(artifactName, eltName, defVariant, null, null, env);
|
|
441
|
+
: renderElement(artifactName, eltName, defVariant, null, null, activateAlterMode(env));
|
|
438
442
|
}
|
|
439
443
|
function getEltStrNoProps(defVariant, eltName, ...props) {
|
|
440
444
|
const defNoProps = Object.assign({}, defVariant);
|
|
@@ -573,7 +577,7 @@ function toSqlDdl(csn, options) {
|
|
|
573
577
|
const { front, back } = getSqlSnippets(options, art);
|
|
574
578
|
let result = front;
|
|
575
579
|
// Only HANA has row/column tables
|
|
576
|
-
if (options.
|
|
580
|
+
if (options.sqlDialect === 'hana') {
|
|
577
581
|
if (hanaTc && hanaTc.storeType) {
|
|
578
582
|
// Explicitly specified
|
|
579
583
|
result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
|
|
@@ -604,7 +608,7 @@ function toSqlDdl(csn, options) {
|
|
|
604
608
|
// for `to.sql` w/ dialect `hana` the constraints will be part of the
|
|
605
609
|
const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
|
|
606
610
|
if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
|
|
607
|
-
const renderReferentialConstraintsAsHdbconstraint = options.
|
|
611
|
+
const renderReferentialConstraintsAsHdbconstraint = options.src === 'hdi';
|
|
608
612
|
const referentialConstraints = {};
|
|
609
613
|
Object.entries(art.$tableConstraints.referential)
|
|
610
614
|
.forEach(([ fileName, referentialConstraint ]) => {
|
|
@@ -627,7 +631,7 @@ function toSqlDdl(csn, options) {
|
|
|
627
631
|
const uniqueConstraints = art.$tableConstraints && art.$tableConstraints.unique;
|
|
628
632
|
for (const cn in uniqueConstraints) {
|
|
629
633
|
const c = uniqueConstraints[cn];
|
|
630
|
-
if (options.
|
|
634
|
+
if (options.src === 'hdi') {
|
|
631
635
|
resultObj.hdbindex[`${artifactName}.${cn}`]
|
|
632
636
|
= `UNIQUE INVERTED INDEX ${renderArtifactName(`${artifactName}_${cn}`)} ON ${tableName} (${c.map(cpath => quoteSqlId(cpath.ref[0])).join(', ')})`;
|
|
633
637
|
}
|
|
@@ -637,20 +641,20 @@ function toSqlDdl(csn, options) {
|
|
|
637
641
|
}
|
|
638
642
|
result += `${env.indent}\n)`;
|
|
639
643
|
|
|
640
|
-
if (options.
|
|
644
|
+
if (options.sqlDialect === 'hana')
|
|
641
645
|
result += renderTechnicalConfiguration(art.technicalConfig, childEnv);
|
|
642
646
|
|
|
643
647
|
|
|
644
648
|
const associations = Object.keys(art.elements).map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
645
649
|
.filter(s => s !== '')
|
|
646
650
|
.join(',\n');
|
|
647
|
-
if (associations !== '' && options.
|
|
651
|
+
if (associations !== '' && options.sqlDialect === 'hana') {
|
|
648
652
|
result += `${env.indent} WITH ASSOCIATIONS (\n${associations}\n`;
|
|
649
653
|
result += `${env.indent})`;
|
|
650
654
|
}
|
|
651
655
|
// Only HANA has indices
|
|
652
656
|
// FIXME: Really? We should provide a DB-agnostic way to specify that
|
|
653
|
-
if (options.
|
|
657
|
+
if (options.sqlDialect === 'hana')
|
|
654
658
|
renderIndexesInto(art.technicalConfig && art.technicalConfig.hana.indexes, artifactName, resultObj, env);
|
|
655
659
|
|
|
656
660
|
if (options.sqlDialect === 'hana' && hasHanaComment(art, options))
|
|
@@ -753,12 +757,12 @@ function toSqlDdl(csn, options) {
|
|
|
753
757
|
duplicateChecker.addElement(quotedElementName, elm.$location, elementName);
|
|
754
758
|
|
|
755
759
|
let result = `${env.indent + quotedElementName} ${renderTypeReference(artifactName, elementName, elm)
|
|
756
|
-
}${renderNullability(elm, true)}`;
|
|
760
|
+
}${renderNullability(elm, true, env.alterMode)}`;
|
|
757
761
|
if (elm.default)
|
|
758
762
|
result += ` DEFAULT ${renderExpr(elm.default, env)}`;
|
|
759
763
|
|
|
760
764
|
// Only HANA has fuzzy indices
|
|
761
|
-
if (fzindex && options.
|
|
765
|
+
if (fzindex && options.sqlDialect === 'hana')
|
|
762
766
|
result += ` ${renderExpr(fzindex, env)}`;
|
|
763
767
|
|
|
764
768
|
// (table) elements can only have a @sql.append
|
|
@@ -906,7 +910,7 @@ function toSqlDdl(csn, options) {
|
|
|
906
910
|
throw new ModelError(`Unexpected form of index: "${index}"`);
|
|
907
911
|
|
|
908
912
|
let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
|
|
909
|
-
if (options.
|
|
913
|
+
if (options.sqlMapping === 'plain')
|
|
910
914
|
indexName = indexName.replace(/(\.|::)/g, '_');
|
|
911
915
|
|
|
912
916
|
const result = index.slice(0, i + 1); // CREATE UNIQUE INDEX
|
|
@@ -945,7 +949,7 @@ function toSqlDdl(csn, options) {
|
|
|
945
949
|
let result = `${renderViewSource(artifactName, source.args[0], env)}`;
|
|
946
950
|
for (let i = 1; i < source.args.length; i++) {
|
|
947
951
|
result = `(${result} ${source.join.toUpperCase()} `;
|
|
948
|
-
if (options.
|
|
952
|
+
if (options.sqlDialect === 'hana')
|
|
949
953
|
result += renderJoinCardinality(source.cardinality);
|
|
950
954
|
result += `JOIN ${renderViewSource(artifactName, source.args[i], env)}`;
|
|
951
955
|
if (source.on)
|
|
@@ -1008,7 +1012,7 @@ function toSqlDdl(csn, options) {
|
|
|
1008
1012
|
let result = renderAbsolutePath(path, ':', env);
|
|
1009
1013
|
|
|
1010
1014
|
// Take care of aliases
|
|
1011
|
-
const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.
|
|
1015
|
+
const implicitAlias = path.ref.length === 0 ? getLastPartOf(getResultingName(csn, options.sqlMapping, path.ref[0])) : getLastPartOfRef(path.ref);
|
|
1012
1016
|
if (path.as) {
|
|
1013
1017
|
// Source had an alias - render it
|
|
1014
1018
|
result += ` AS ${quoteSqlId(path.as)}`;
|
|
@@ -1126,7 +1130,7 @@ function toSqlDdl(csn, options) {
|
|
|
1126
1130
|
let result = '';
|
|
1127
1131
|
const leaf = col.as || col.ref && col.ref[col.ref.length - 1] || col.func;
|
|
1128
1132
|
if (leaf && elements[leaf] && elements[leaf].virtual) {
|
|
1129
|
-
if (isDeprecatedEnabled(options, '
|
|
1133
|
+
if (isDeprecatedEnabled(options, '_renderVirtualElements'))
|
|
1130
1134
|
// render a virtual column 'null as <alias>'
|
|
1131
1135
|
result += `${env.indent}NULL AS ${quoteSqlId(col.as || leaf)}`;
|
|
1132
1136
|
}
|
|
@@ -1165,7 +1169,7 @@ function toSqlDdl(csn, options) {
|
|
|
1165
1169
|
.map(name => renderAssociationElement(name, art.elements[name], childEnv))
|
|
1166
1170
|
.filter(s => s !== '')
|
|
1167
1171
|
.join(',\n');
|
|
1168
|
-
if (associations !== '' && options.
|
|
1172
|
+
if (associations !== '' && options.sqlDialect === 'hana') {
|
|
1169
1173
|
result += `${env.indent}\nWITH ASSOCIATIONS (\n${associations}\n`;
|
|
1170
1174
|
result += `${env.indent})`;
|
|
1171
1175
|
}
|
|
@@ -1197,7 +1201,7 @@ function toSqlDdl(csn, options) {
|
|
|
1197
1201
|
// this would be an incompatible change, as non-uppercased, quoted identifiers
|
|
1198
1202
|
// are rejected by the HANA compiler.
|
|
1199
1203
|
let pIdentifier;
|
|
1200
|
-
if (options.
|
|
1204
|
+
if (options.sqlMapping === 'quoted' || options.sqlMapping === 'hdbcds')
|
|
1201
1205
|
pIdentifier = prepareIdentifier(pn);
|
|
1202
1206
|
else
|
|
1203
1207
|
pIdentifier = quoteSqlId(pn);
|
|
@@ -1386,7 +1390,7 @@ function toSqlDdl(csn, options) {
|
|
|
1386
1390
|
'cds.LocalTime': 'cds.Time',
|
|
1387
1391
|
};
|
|
1388
1392
|
const tName = forHanaRenamesToEarly[typeName] || typeName;
|
|
1389
|
-
const types = cdsToSqlTypes[options.
|
|
1393
|
+
const types = cdsToSqlTypes[options.sqlDialect];
|
|
1390
1394
|
return types && types[tName] || cdsToSqlTypes.standard[tName] || 'CHAR';
|
|
1391
1395
|
}
|
|
1392
1396
|
|
|
@@ -1395,9 +1399,15 @@ function toSqlDdl(csn, options) {
|
|
|
1395
1399
|
*
|
|
1396
1400
|
* @param {object} obj Object to render for
|
|
1397
1401
|
* @param {boolean} treatKeyAsNotNull Whether to render KEY as not null
|
|
1402
|
+
* @param {boolean} deltaMode Look for a $notNull and use that with precedence over notNull
|
|
1398
1403
|
* @returns {string} NULL/NOT NULL or ''
|
|
1399
1404
|
*/
|
|
1400
|
-
function renderNullability(obj, treatKeyAsNotNull = false) {
|
|
1405
|
+
function renderNullability(obj, treatKeyAsNotNull = false, deltaMode = false) {
|
|
1406
|
+
if (deltaMode && obj.$notNull !== undefined) { // can be set via compare.js if it goes from "not null" to implicit "null"
|
|
1407
|
+
return obj.$notNull ? ' NOT NULL' : ' NULL';
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
|
|
1401
1411
|
if (obj.notNull === undefined && !(obj.key && treatKeyAsNotNull)) {
|
|
1402
1412
|
// Attribute not set at all
|
|
1403
1413
|
return '';
|
|
@@ -1426,7 +1436,7 @@ function toSqlDdl(csn, options) {
|
|
|
1426
1436
|
|
|
1427
1437
|
if (elm.srid !== undefined) {
|
|
1428
1438
|
// SAP HANA Geometry types translate into CHAR in plain/sqlite (give them the default length of 2000)
|
|
1429
|
-
if (options.
|
|
1439
|
+
if (options.sqlDialect !== 'hana')
|
|
1430
1440
|
params.push(2000);
|
|
1431
1441
|
else
|
|
1432
1442
|
params.push(elm.srid);
|
|
@@ -1448,7 +1458,7 @@ function toSqlDdl(csn, options) {
|
|
|
1448
1458
|
case 'date':
|
|
1449
1459
|
case 'time':
|
|
1450
1460
|
case 'timestamp':
|
|
1451
|
-
if (options.
|
|
1461
|
+
if (options.sqlDialect === 'sqlite') {
|
|
1452
1462
|
// simple string literal '2017-11-02'
|
|
1453
1463
|
return `'${x.val}'`;
|
|
1454
1464
|
}
|
|
@@ -1474,7 +1484,7 @@ function toSqlDdl(csn, options) {
|
|
|
1474
1484
|
|
|
1475
1485
|
if (x.ref[0] === '$user') {
|
|
1476
1486
|
if (magicReplacement !== null)
|
|
1477
|
-
return
|
|
1487
|
+
return renderStringForSql(magicReplacement, options.sqlDialect);
|
|
1478
1488
|
|
|
1479
1489
|
const result = render$user();
|
|
1480
1490
|
// Invalid second path step doesn't cause a return
|
|
@@ -1488,7 +1498,19 @@ function toSqlDdl(csn, options) {
|
|
|
1488
1498
|
return result;
|
|
1489
1499
|
}
|
|
1490
1500
|
else if (x.ref[0] === '$session' && magicReplacement !== null) {
|
|
1491
|
-
return
|
|
1501
|
+
return renderStringForSql(magicReplacement, options.sqlDialect);
|
|
1502
|
+
}
|
|
1503
|
+
else if (x.ref[0] === '$now') { // TODO: Can there be cases where $now is followed by something?
|
|
1504
|
+
switch (options.sqlDialect) {
|
|
1505
|
+
case 'plain':
|
|
1506
|
+
case 'sqlite':
|
|
1507
|
+
case 'hana':
|
|
1508
|
+
return 'CURRENT_TIMESTAMP';
|
|
1509
|
+
case 'postgres':
|
|
1510
|
+
return 'current_timestamp';
|
|
1511
|
+
default:
|
|
1512
|
+
return quoteSqlId(x.ref[0]);
|
|
1513
|
+
}
|
|
1492
1514
|
}
|
|
1493
1515
|
}
|
|
1494
1516
|
// FIXME: We currently cannot distinguish whether '$parameters' was quoted or not - we
|
|
@@ -1508,29 +1530,19 @@ function toSqlDdl(csn, options) {
|
|
|
1508
1530
|
* @returns {string|null} Null in case of an invalid second path step
|
|
1509
1531
|
*/
|
|
1510
1532
|
function render$user() {
|
|
1511
|
-
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1533
|
+
// FIXME: this is all not enough: we might need an explicit select item alias (?)
|
|
1512
1534
|
if (x.ref[1] === 'id') {
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
else if ((options.toSql.user && options.toSql.user.id) && (typeof options.toSql.user.id === 'string' || options.toSql.user.id instanceof String))
|
|
1518
|
-
return `'${options.toSql.user.id}'`;
|
|
1519
|
-
|
|
1520
|
-
if (options.toSql.dialect === 'sqlite' || options.toSql.dialect === 'plain') {
|
|
1521
|
-
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1522
|
-
return '\'$user.id\'';
|
|
1523
|
-
}
|
|
1524
|
-
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1535
|
+
if (options.sqlDialect === 'hana')
|
|
1536
|
+
return 'SESSION_CONTEXT(\'APPLICATIONUSER\')';
|
|
1537
|
+
warning(null, null, 'The "$user" variable is not supported. Use option "variableReplacements" to specify a value for "$user.id"');
|
|
1538
|
+
return '\'$user.id\'';
|
|
1525
1539
|
}
|
|
1526
1540
|
else if (x.ref[1] === 'locale') {
|
|
1527
|
-
if (options.
|
|
1528
|
-
return (
|
|
1529
|
-
|
|
1530
|
-
}
|
|
1531
|
-
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1541
|
+
if (options.sqlDialect === 'hana')
|
|
1542
|
+
return 'SESSION_CONTEXT(\'LOCALE\')';
|
|
1543
|
+
return '\'en\''; // default language
|
|
1532
1544
|
}
|
|
1533
|
-
// Basically: Second path step was invalid, do nothing - should not happen
|
|
1545
|
+
// Basically: Second path step was invalid, do nothing - should not happen.
|
|
1534
1546
|
return null;
|
|
1535
1547
|
}
|
|
1536
1548
|
/**
|
|
@@ -1547,13 +1559,14 @@ function toSqlDdl(csn, options) {
|
|
|
1547
1559
|
*/
|
|
1548
1560
|
function render$at() {
|
|
1549
1561
|
if (x.ref[1] === 'from') {
|
|
1550
|
-
switch (options.
|
|
1562
|
+
switch (options.sqlDialect) {
|
|
1551
1563
|
case 'sqlite': {
|
|
1552
1564
|
const dateFromFormat = '%Y-%m-%dT%H:%M:%S.000Z';
|
|
1553
1565
|
return `strftime('${dateFromFormat}', 'now')`;
|
|
1554
1566
|
}
|
|
1555
1567
|
case 'hana':
|
|
1556
1568
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-FROM\'))';
|
|
1569
|
+
case 'postgres':
|
|
1557
1570
|
case 'plain':
|
|
1558
1571
|
return 'current_timestamp';
|
|
1559
1572
|
default:
|
|
@@ -1562,7 +1575,7 @@ function toSqlDdl(csn, options) {
|
|
|
1562
1575
|
}
|
|
1563
1576
|
|
|
1564
1577
|
if (x.ref[1] === 'to') {
|
|
1565
|
-
switch (options.
|
|
1578
|
+
switch (options.sqlDialect) {
|
|
1566
1579
|
case 'sqlite': {
|
|
1567
1580
|
// + 1ms compared to $at.from
|
|
1568
1581
|
const dateToFormat = '%Y-%m-%dT%H:%M:%S.001Z';
|
|
@@ -1570,6 +1583,7 @@ function toSqlDdl(csn, options) {
|
|
|
1570
1583
|
}
|
|
1571
1584
|
case 'hana':
|
|
1572
1585
|
return 'TO_TIMESTAMP(SESSION_CONTEXT(\'VALID-TO\'))';
|
|
1586
|
+
case 'postgres':
|
|
1573
1587
|
case 'plain':
|
|
1574
1588
|
return 'current_timestamp';
|
|
1575
1589
|
default:
|
|
@@ -1591,7 +1605,6 @@ function toSqlDdl(csn, options) {
|
|
|
1591
1605
|
if (typeof (s) === 'string') {
|
|
1592
1606
|
// TODO: When is this actually executed and not handled already in renderExpr?
|
|
1593
1607
|
const magicForHana = {
|
|
1594
|
-
$now: 'CURRENT_TIMESTAMP',
|
|
1595
1608
|
'$user.id': 'SESSION_CONTEXT(\'APPLICATIONUSER\')',
|
|
1596
1609
|
'$user.locale': 'SESSION_CONTEXT(\'LOCALE\')',
|
|
1597
1610
|
};
|
|
@@ -1599,7 +1612,7 @@ function toSqlDdl(csn, options) {
|
|
|
1599
1612
|
if (idx === 0) {
|
|
1600
1613
|
// HANA-specific translation of '$now' and '$user'
|
|
1601
1614
|
// FIXME: this is all not enough: we might need an explicit select item alias
|
|
1602
|
-
if (magicForHana[s])
|
|
1615
|
+
if (options.sqlDialect === 'hana' && magicForHana[s])
|
|
1603
1616
|
return magicForHana[s];
|
|
1604
1617
|
|
|
1605
1618
|
// Ignore initial $projection and initial $self
|
|
@@ -1652,6 +1665,15 @@ function toSqlDdl(csn, options) {
|
|
|
1652
1665
|
function increaseIndent(env) {
|
|
1653
1666
|
return Object.assign({}, env, { indent: `${env.indent} ` });
|
|
1654
1667
|
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Returns a copy of 'env' with alterMode set to true
|
|
1670
|
+
*
|
|
1671
|
+
* @param {object} env Render environment
|
|
1672
|
+
* @returns {object} Render environment with alterMode
|
|
1673
|
+
*/
|
|
1674
|
+
function activateAlterMode(env) {
|
|
1675
|
+
return Object.assign({ alterMode: true }, env);
|
|
1676
|
+
}
|
|
1655
1677
|
}
|
|
1656
1678
|
|
|
1657
1679
|
/**
|