@sap/cds-compiler 2.12.0 → 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 +110 -15
- package/bin/cdsc.js +13 -13
- package/bin/cdsse.js +2 -2
- package/doc/CHANGELOG_BETA.md +13 -6
- package/doc/CHANGELOG_DEPRECATED.md +22 -6
- package/doc/NameResolution.md +21 -16
- package/lib/api/main.js +28 -63
- package/lib/api/options.js +3 -3
- package/lib/api/validate.js +0 -5
- package/lib/backends.js +15 -23
- package/lib/base/dictionaries.js +0 -8
- package/lib/base/error.js +26 -0
- package/lib/base/keywords.js +7 -17
- package/lib/base/location.js +9 -4
- package/lib/base/message-registry.js +25 -4
- package/lib/base/messages.js +16 -26
- package/lib/base/model.js +2 -63
- package/lib/base/optionProcessorHelper.js +158 -123
- package/lib/checks/annotationsOData.js +1 -1
- package/lib/checks/cdsPersistence.js +2 -1
- package/lib/checks/enricher.js +17 -1
- package/lib/checks/invalidTarget.js +3 -1
- package/lib/checks/managedWithoutKeys.js +3 -1
- package/lib/checks/selectItems.js +4 -4
- package/lib/checks/sql-snippets.js +27 -26
- package/lib/checks/types.js +1 -1
- package/lib/checks/validator.js +4 -7
- package/lib/compiler/assert-consistency.js +5 -3
- package/lib/compiler/builtins.js +8 -6
- package/lib/compiler/checks.js +14 -3
- 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 +32 -13
- 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 +111 -46
- package/lib/compiler/resolve.js +1433 -0
- package/lib/compiler/shared.js +64 -37
- package/lib/compiler/tweak-assocs.js +529 -0
- package/lib/compiler/utils.js +197 -33
- package/lib/edm/.eslintrc.json +5 -0
- package/lib/edm/annotations/genericTranslation.js +5 -9
- package/lib/edm/annotations/preprocessAnnotations.js +2 -2
- package/lib/edm/csn2edm.js +9 -8
- package/lib/edm/edm.js +11 -12
- package/lib/edm/edmPreprocessor.js +137 -73
- package/lib/edm/edmUtils.js +116 -22
- package/lib/gen/Dictionary.json +10 -3
- package/lib/gen/language.checksum +1 -1
- package/lib/gen/language.interp +9 -1
- package/lib/gen/language.tokens +86 -83
- package/lib/gen/languageLexer.interp +10 -1
- package/lib/gen/languageLexer.js +860 -833
- package/lib/gen/languageLexer.tokens +78 -75
- package/lib/gen/languageParser.js +5282 -4265
- package/lib/json/from-csn.js +12 -1
- package/lib/json/to-csn.js +126 -66
- package/lib/language/docCommentParser.js +2 -2
- package/lib/language/genericAntlrParser.js +76 -3
- package/lib/language/language.g4 +297 -130
- package/lib/language/multiLineStringParser.js +5 -5
- package/lib/main.d.ts +468 -59
- package/lib/main.js +35 -9
- package/lib/model/api.js +3 -1
- package/lib/model/csnRefs.js +225 -156
- package/lib/model/csnUtils.js +192 -223
- package/lib/model/enrichCsn.js +70 -29
- package/lib/model/revealInternalProperties.js +27 -6
- package/lib/model/sortViews.js +2 -1
- package/lib/modelCompare/compare.js +17 -12
- package/lib/optionProcessor.js +5 -4
- package/lib/render/manageConstraints.js +35 -32
- package/lib/render/toCdl.js +73 -288
- package/lib/render/toHdbcds.js +25 -23
- package/lib/render/toSql.js +98 -41
- package/lib/render/utils/common.js +5 -10
- package/lib/render/utils/sql.js +4 -3
- 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/db/.eslintrc.json +2 -0
- package/lib/transform/db/applyTransformations.js +35 -12
- package/lib/transform/db/assertUnique.js +1 -1
- package/lib/transform/db/associations.js +103 -305
- package/lib/transform/db/cdsPersistence.js +2 -2
- package/lib/transform/db/constraints.js +55 -52
- package/lib/transform/db/expansion.js +46 -24
- package/lib/transform/db/flattening.js +553 -102
- package/lib/transform/db/groupByOrderBy.js +3 -1
- package/lib/transform/db/transformExists.js +59 -6
- package/lib/transform/db/views.js +5 -4
- package/lib/transform/draft/.eslintrc.json +38 -0
- package/lib/transform/{db/draft.js → draft/db.js} +6 -5
- package/lib/transform/draft/odata.js +227 -0
- package/lib/transform/forHanaNew.js +67 -183
- package/lib/transform/forOdataNew.js +17 -171
- package/lib/transform/localized.js +34 -19
- package/lib/transform/odata/generateForeignKeyElements.js +1 -1
- 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 +36 -22
- package/lib/transform/translateAssocsToJoins.js +2 -19
- 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/objectUtils.js +30 -0
- package/package.json +1 -1
- package/share/messages/README.md +26 -0
- package/lib/compiler/definer.js +0 -2361
- package/lib/compiler/resolver.js +0 -3079
- package/lib/transform/universalCsnEnricher.js +0 -237
package/lib/render/toHdbcds.js
CHANGED
|
@@ -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';
|
|
@@ -152,7 +153,6 @@ function toHdbcdsSource(csn, options) {
|
|
|
152
153
|
|
|
153
154
|
switch (art.kind) {
|
|
154
155
|
case 'entity':
|
|
155
|
-
case 'view':
|
|
156
156
|
// FIXME: For HANA CDS, we need to replace $self at the beginning of paths in association ON-condition
|
|
157
157
|
// by the full name of the artifact we are rendering (should actually be done by forHana, but that is
|
|
158
158
|
// somewhat difficult because this kind of absolute path is quite unusual). In order not to have to pass
|
|
@@ -177,7 +177,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
177
177
|
case 'event':
|
|
178
178
|
return '';
|
|
179
179
|
default:
|
|
180
|
-
throw new
|
|
180
|
+
throw new ModelError(`Unknown artifact kind: ${art.kind}`);
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
|
|
@@ -213,7 +213,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
213
213
|
}
|
|
214
214
|
|
|
215
215
|
/**
|
|
216
|
-
* Check
|
|
216
|
+
* Check whether the given context is the direct parent of the containee.
|
|
217
217
|
*
|
|
218
218
|
* @param {string} containee Name of the contained artifact
|
|
219
219
|
* @param {string} contextName Name of the (grand?)parent context
|
|
@@ -313,7 +313,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
313
313
|
return `${result + renderedSubArtifacts + env.indent}};\n`;
|
|
314
314
|
}
|
|
315
315
|
/**
|
|
316
|
-
* Check
|
|
316
|
+
* Check whether the given context is shadowed, i.e. part of his name prefix is shared by a
|
|
317
317
|
* non-context/service/namespace definition
|
|
318
318
|
*
|
|
319
319
|
* @param {string} artifactName
|
|
@@ -469,7 +469,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
469
469
|
// in 'tc' that we find and render them all (is it syntactically allowed yet to have more than one?)
|
|
470
470
|
tc = tc.hana;
|
|
471
471
|
if (!tc)
|
|
472
|
-
throw new
|
|
472
|
+
throw new ModelError('Expecting a HANA technical configuration');
|
|
473
473
|
|
|
474
474
|
result += `\n${env.indent}technical ${tc.calculated ? '' : 'hana '}configuration {\n`;
|
|
475
475
|
|
|
@@ -544,7 +544,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
544
544
|
* @param {CSN.Element} elm Content of the element
|
|
545
545
|
* @param {CdlRenderEnvironment} env Environment
|
|
546
546
|
* @param {DuplicateChecker} [duplicateChecker] Utility for detecting duplicates
|
|
547
|
-
* @param {boolean} [isSubElement]
|
|
547
|
+
* @param {boolean} [isSubElement] Whether the given element is a subelement or not - subelements cannot be key!
|
|
548
548
|
* @returns {string} The rendered element
|
|
549
549
|
*/
|
|
550
550
|
function renderElement(elementName, elm, env, duplicateChecker, isSubElement) {
|
|
@@ -628,7 +628,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
628
628
|
function renderAbsolutePath(path, env) {
|
|
629
629
|
// Sanity checks
|
|
630
630
|
if (!path.ref)
|
|
631
|
-
throw new
|
|
631
|
+
throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
|
|
632
632
|
|
|
633
633
|
|
|
634
634
|
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
@@ -791,7 +791,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
791
791
|
* or 'entity')
|
|
792
792
|
*
|
|
793
793
|
* @param {CSN.Query} query Query object
|
|
794
|
-
* @param {boolean} isLeadingQuery
|
|
794
|
+
* @param {boolean} isLeadingQuery Whether the query is the leading query or not
|
|
795
795
|
* @param {CdlRenderEnvironment} env Environment
|
|
796
796
|
* @param {CSN.Path} [path=[]] CSN path to the query
|
|
797
797
|
* @param {object} [elements] For leading query, the elements of the artifact
|
|
@@ -823,7 +823,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
823
823
|
}
|
|
824
824
|
// Otherwise must have a SELECT
|
|
825
825
|
else if (!query.SELECT) {
|
|
826
|
-
throw new
|
|
826
|
+
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
827
827
|
}
|
|
828
828
|
const select = query.SELECT;
|
|
829
829
|
const childEnv = increaseIndent(env);
|
|
@@ -870,7 +870,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
870
870
|
alreadyRendered += `${continueIndent(alreadyRendered, env)}where ${renderExpr(select.where, env, true, true)}`;
|
|
871
871
|
|
|
872
872
|
if (select.groupBy)
|
|
873
|
-
alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
|
|
873
|
+
alreadyRendered += `${continueIndent(alreadyRendered, env)}group by ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
|
|
874
874
|
|
|
875
875
|
if (select.having)
|
|
876
876
|
alreadyRendered += `${continueIndent(alreadyRendered, env)}having ${renderExpr(select.having, env, true, true)}`;
|
|
@@ -927,7 +927,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
927
927
|
* @returns {string} Rendered order by
|
|
928
928
|
*/
|
|
929
929
|
function renderOrderByEntry(entry, env) {
|
|
930
|
-
let result = renderExpr(entry, env);
|
|
930
|
+
let result = renderExpr(entry, env, true, false, true);
|
|
931
931
|
if (entry.sort)
|
|
932
932
|
result += ` ${entry.sort}`;
|
|
933
933
|
|
|
@@ -1011,7 +1011,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1011
1011
|
// Anonymous structured type
|
|
1012
1012
|
if (!elm.type) {
|
|
1013
1013
|
if (!elm.elements)
|
|
1014
|
-
throw new
|
|
1014
|
+
throw new ModelError(`Missing type of: ${JSON.stringify(elm)}`);
|
|
1015
1015
|
|
|
1016
1016
|
result += '{\n';
|
|
1017
1017
|
const childEnv = increaseIndent(env);
|
|
@@ -1116,15 +1116,17 @@ function toHdbcdsSource(csn, options) {
|
|
|
1116
1116
|
* @param {CdlRenderEnvironment} env Environment
|
|
1117
1117
|
* @param {boolean} [inline=true] Whether to render inline
|
|
1118
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.
|
|
1119
1121
|
* @returns {string} Rendered expression
|
|
1120
1122
|
*/
|
|
1121
|
-
function renderExpr(expr, env, inline = true, inExpr = false) {
|
|
1123
|
+
function renderExpr(expr, env, inline = true, inExpr = false, alwaysRenderCast = false) {
|
|
1122
1124
|
// Compound expression
|
|
1123
1125
|
if (Array.isArray(expr))
|
|
1124
1126
|
return beautifyExprArray(expr.map(item => renderExpr(item, env, inline, inExpr)));
|
|
1125
1127
|
|
|
1126
1128
|
if (typeof expr === 'object' && expr !== null) {
|
|
1127
|
-
if (inExpr && expr.cast && expr.cast.type)
|
|
1129
|
+
if ((inExpr || alwaysRenderCast) && expr.cast && expr.cast.type)
|
|
1128
1130
|
return renderExplicitTypeCast(renderExprObject(expr));
|
|
1129
1131
|
return renderExprObject(expr);
|
|
1130
1132
|
}
|
|
@@ -1186,7 +1188,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1186
1188
|
return `${renderQuery(x, false, increaseIndent(env))}`;
|
|
1187
1189
|
}
|
|
1188
1190
|
|
|
1189
|
-
throw new
|
|
1191
|
+
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1190
1192
|
}
|
|
1191
1193
|
/**
|
|
1192
1194
|
* @param {object} x Expression with a val and/or literal property
|
|
@@ -1212,7 +1214,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1212
1214
|
|
|
1213
1215
|
// otherwise fall through to
|
|
1214
1216
|
default:
|
|
1215
|
-
throw new
|
|
1217
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1216
1218
|
}
|
|
1217
1219
|
}
|
|
1218
1220
|
|
|
@@ -1319,7 +1321,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1319
1321
|
else if (typeof s === 'object') {
|
|
1320
1322
|
// Sanity check
|
|
1321
1323
|
if (!s.func && !s.id)
|
|
1322
|
-
throw new
|
|
1324
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1323
1325
|
|
|
1324
1326
|
// Not really a path step but an object-like function call
|
|
1325
1327
|
if (s.func)
|
|
@@ -1338,7 +1340,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1338
1340
|
return result;
|
|
1339
1341
|
}
|
|
1340
1342
|
|
|
1341
|
-
throw new
|
|
1343
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1342
1344
|
}
|
|
1343
1345
|
}
|
|
1344
1346
|
|
|
@@ -1355,15 +1357,15 @@ function toHdbcdsSource(csn, options) {
|
|
|
1355
1357
|
const args = node.args ? node.args : {};
|
|
1356
1358
|
// Positional arguments
|
|
1357
1359
|
if (Array.isArray(args))
|
|
1358
|
-
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1360
|
+
return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
|
|
1359
1361
|
|
|
1360
1362
|
// Named arguments (object/dict)
|
|
1361
1363
|
else if (typeof args === 'object')
|
|
1362
1364
|
// if this is a function param which is not a reference to the model, we must not quote it
|
|
1363
|
-
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(', ');
|
|
1364
1366
|
|
|
1365
1367
|
|
|
1366
|
-
throw new
|
|
1368
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1367
1369
|
}
|
|
1368
1370
|
|
|
1369
1371
|
/**
|
|
@@ -1645,7 +1647,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1645
1647
|
if (plainNames) {
|
|
1646
1648
|
const art = csn.definitions[name];
|
|
1647
1649
|
// For 'plain' naming, take all entities and views, nothing else
|
|
1648
|
-
if (art.kind === 'entity'
|
|
1650
|
+
if (art.kind === 'entity')
|
|
1649
1651
|
result[name] = art;
|
|
1650
1652
|
}
|
|
1651
1653
|
else {
|
|
@@ -1736,7 +1738,7 @@ function toHdbcdsSource(csn, options) {
|
|
|
1736
1738
|
function quoteId(id) {
|
|
1737
1739
|
// Should only ever be called for real IDs (i.e. no dots inside)
|
|
1738
1740
|
if (id.indexOf('.') !== -1)
|
|
1739
|
-
throw new
|
|
1741
|
+
throw new ModelError(id);
|
|
1740
1742
|
|
|
1741
1743
|
|
|
1742
1744
|
switch (options.forHana.names) {
|
package/lib/render/toSql.js
CHANGED
|
@@ -21,6 +21,7 @@ const { isBetaEnabled, isDeprecatedEnabled } = require('../base/model');
|
|
|
21
21
|
const { smartFuncId } = require('../sql-identifier');
|
|
22
22
|
const { sortCsn } = require('../json/to-csn');
|
|
23
23
|
const { manageConstraints } = require('./manageConstraints');
|
|
24
|
+
const { ModelError } = require('../base/error');
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -172,6 +173,12 @@ function toSqlDdl(csn, options) {
|
|
|
172
173
|
comment(comment) {
|
|
173
174
|
return comment && `'${comment.replace(/'/g, '\'\'')}'` || 'NULL';
|
|
174
175
|
},
|
|
176
|
+
/*
|
|
177
|
+
Alter SQL snippet for entity.
|
|
178
|
+
*/
|
|
179
|
+
alterEntitySqlSnippet(tableName, snippet) {
|
|
180
|
+
return [ `ALTER TABLE ${tableName} ${snippet};` ];
|
|
181
|
+
},
|
|
175
182
|
/*
|
|
176
183
|
Concatenate multiple statements which are to be treated as one by the API caller.
|
|
177
184
|
*/
|
|
@@ -284,8 +291,8 @@ function toSqlDdl(csn, options) {
|
|
|
284
291
|
delete mainResultObj[hdbKind];
|
|
285
292
|
}
|
|
286
293
|
|
|
287
|
-
// add `ALTER TABLE ADD CONSTRAINT` statements
|
|
288
|
-
if (options.
|
|
294
|
+
// add `ALTER TABLE ADD CONSTRAINT` statements per default for `to.sql` w/ dialect `hana`
|
|
295
|
+
if (!options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana') {
|
|
289
296
|
const alterStmts = manageConstraints(csn, options);
|
|
290
297
|
|
|
291
298
|
for ( const constraintName of Object.keys(alterStmts))
|
|
@@ -318,7 +325,6 @@ function toSqlDdl(csn, options) {
|
|
|
318
325
|
|
|
319
326
|
switch (art.kind) {
|
|
320
327
|
case 'entity':
|
|
321
|
-
case 'view':
|
|
322
328
|
if (getNormalizedQuery(art).query) {
|
|
323
329
|
const result = renderView(artifactName, art, env);
|
|
324
330
|
if (result)
|
|
@@ -340,7 +346,7 @@ function toSqlDdl(csn, options) {
|
|
|
340
346
|
// Ignore: not SQL-relevant
|
|
341
347
|
return;
|
|
342
348
|
default:
|
|
343
|
-
throw new
|
|
349
|
+
throw new ModelError(`Unknown artifact kind: ${art.kind}`);
|
|
344
350
|
}
|
|
345
351
|
}
|
|
346
352
|
|
|
@@ -361,7 +367,7 @@ function toSqlDdl(csn, options) {
|
|
|
361
367
|
renderExtendInto(artifactName, artifact.elements, ext.elements, resultObj, env, extensionsDuplicateChecker);
|
|
362
368
|
|
|
363
369
|
if (!artifactName)
|
|
364
|
-
throw new
|
|
370
|
+
throw new ModelError(`Undefined artifact name: ${artifactName}`);
|
|
365
371
|
}
|
|
366
372
|
|
|
367
373
|
// Render an artifact deletion into the appropriate dictionary of 'resultObj'.
|
|
@@ -401,11 +407,25 @@ function toSqlDdl(csn, options) {
|
|
|
401
407
|
? renderAssociationElement(eltName, defVariant, env)
|
|
402
408
|
: renderElement(artifactName, eltName, defVariant, null, null, env);
|
|
403
409
|
}
|
|
404
|
-
function
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
410
|
+
function getEltStrNoProps(defVariant, eltName, ...props) {
|
|
411
|
+
const defNoProps = Object.assign({}, defVariant);
|
|
412
|
+
for (const prop of props)
|
|
413
|
+
delete defNoProps[prop];
|
|
414
|
+
return getEltStr(defNoProps, eltName);
|
|
408
415
|
}
|
|
416
|
+
function oldAnnoChangedIncompatibly(defOld, defNew) {
|
|
417
|
+
return typeof defOld === 'string' && defOld.trim().length && !(typeof defNew === 'string' && defNew.trim().startsWith(`${defOld.trim()} `));
|
|
418
|
+
}
|
|
419
|
+
function getUnknownSqlReason(anno, artifactName, defOld, defNew, eltName) {
|
|
420
|
+
const changeKind = defNew === undefined
|
|
421
|
+
? `removed (previous value: ${JSON.stringify(defOld)})`
|
|
422
|
+
: `changed from ${JSON.stringify(defOld)} to ${JSON.stringify(defNew)}`;
|
|
423
|
+
return eltName
|
|
424
|
+
? `annotation ${anno} of element ${artifactName}:${eltName} has been ${changeKind}`
|
|
425
|
+
: `annotation ${anno} of artifact ${artifactName} has been ${changeKind}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const sqlSnippetAnnos = [ '@sql.prepend', '@sql.append' ];
|
|
409
429
|
|
|
410
430
|
const tableName = renderArtifactName(artifactName);
|
|
411
431
|
|
|
@@ -416,6 +436,15 @@ function toSqlDdl(csn, options) {
|
|
|
416
436
|
const alterComment = render.alterEntityComment(tableName, def.new);
|
|
417
437
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
418
438
|
}
|
|
439
|
+
else if (sqlSnippetAnnos.includes(prop)) { // NOTE: @sql.replace may be supported in the future
|
|
440
|
+
if (oldAnnoChangedIncompatibly(def.old, def.new)) {
|
|
441
|
+
// anno was previously set and current change is not simply an appendix → previous anno would have to be reverted → unknown SQL
|
|
442
|
+
addMigration(resultObj, artifactName, false, null, getUnknownSqlReason(prop, artifactName, def.old, def.new));
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
addMigration(resultObj, artifactName, false, render.alterEntitySqlSnippet(tableName, def.new));
|
|
446
|
+
}
|
|
447
|
+
}
|
|
419
448
|
}
|
|
420
449
|
}
|
|
421
450
|
|
|
@@ -450,9 +479,28 @@ function toSqlDdl(csn, options) {
|
|
|
450
479
|
if (eltStrNew === eltStrOld)
|
|
451
480
|
return; // Prevent spurious migrations, where the column DDL does not change.
|
|
452
481
|
|
|
482
|
+
const annosIncompat = [];
|
|
483
|
+
sqlSnippetAnnos
|
|
484
|
+
.filter(anno => def.old[anno] !== def.new[anno])
|
|
485
|
+
.forEach((anno) => { // NOTE: @sql.replace may be supported in the future
|
|
486
|
+
if (oldAnnoChangedIncompatibly(def.old[anno], def.new[anno])) {
|
|
487
|
+
annosIncompat.push(anno);
|
|
488
|
+
// anno was previously set and current change is not simply an appendix → previous anno would have to be reverted → unknown SQL
|
|
489
|
+
addMigration(resultObj, artifactName, false, null, getUnknownSqlReason(anno, artifactName, def.old[anno], def.new[anno], eltName));
|
|
490
|
+
}
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
if (annosIncompat.length) {
|
|
494
|
+
const eltStrOldNoAnnos = getEltStrNoProps(def.old, eltName, ...annosIncompat);
|
|
495
|
+
const eltStrNewNoAnnos = getEltStrNoProps(def.new, eltName, ...annosIncompat);
|
|
496
|
+
if (eltStrOldNoAnnos === eltStrNewNoAnnos) { // only incompatibly-changed annos were modified
|
|
497
|
+
continue;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
453
501
|
if (def.old.doc !== def.new.doc) {
|
|
454
|
-
const eltStrOldNoDoc =
|
|
455
|
-
const eltStrNewNoDoc =
|
|
502
|
+
const eltStrOldNoDoc = getEltStrNoProps(def.old, eltName, 'doc');
|
|
503
|
+
const eltStrNewNoDoc = getEltStrNoProps(def.new, eltName, 'doc');
|
|
456
504
|
if (eltStrOldNoDoc === eltStrNewNoDoc) { // only `doc` changed
|
|
457
505
|
const alterComment = render.alterColumnComment(tableName, sqlId, def.new.doc);
|
|
458
506
|
addMigration(resultObj, artifactName, false, alterComment);
|
|
@@ -501,7 +549,7 @@ function toSqlDdl(csn, options) {
|
|
|
501
549
|
// Explicitly specified
|
|
502
550
|
result += `${art.technicalConfig.hana.storeType.toUpperCase()} `;
|
|
503
551
|
}
|
|
504
|
-
else {
|
|
552
|
+
else if (!front) {
|
|
505
553
|
// in 'hdbtable' files, COLUMN or ROW is mandatory, and COLUMN is the default
|
|
506
554
|
result += 'COLUMN ';
|
|
507
555
|
}
|
|
@@ -524,7 +572,8 @@ function toSqlDdl(csn, options) {
|
|
|
524
572
|
if (primaryKeys !== '')
|
|
525
573
|
result += `,\n${childEnv.indent}${primaryKeys}`;
|
|
526
574
|
|
|
527
|
-
|
|
575
|
+
// for `to.sql` w/ dialect `hana` the constraints will be part of the
|
|
576
|
+
const constraintsAsAlter = !options.constraintsInCreateTable && options.src === 'sql' && options.sqlDialect === 'hana';
|
|
528
577
|
if ( !constraintsAsAlter && art.$tableConstraints && art.$tableConstraints.referential) {
|
|
529
578
|
const renderReferentialConstraintsAsHdbconstraint = options.toSql.src === 'hdi';
|
|
530
579
|
const referentialConstraints = {};
|
|
@@ -612,10 +661,15 @@ function toSqlDdl(csn, options) {
|
|
|
612
661
|
}
|
|
613
662
|
}
|
|
614
663
|
|
|
615
|
-
function addMigration(resultObj, artifactName, drop, sqlArray) {
|
|
664
|
+
function addMigration(resultObj, artifactName, drop, sqlArray, description) {
|
|
616
665
|
if (!(artifactName in resultObj.migrations))
|
|
617
666
|
resultObj.migrations[artifactName] = [];
|
|
618
667
|
|
|
668
|
+
if (!sqlArray) {
|
|
669
|
+
if (description)
|
|
670
|
+
resultObj.migrations[artifactName].push({ description });
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
619
673
|
const migrations = sqlArray.map(migrationSql => ({ drop, sql: migrationSql }));
|
|
620
674
|
resultObj.migrations[artifactName].push(...migrations);
|
|
621
675
|
}
|
|
@@ -678,15 +732,15 @@ function toSqlDdl(csn, options) {
|
|
|
678
732
|
if (fzindex && options.toSql.dialect === 'hana')
|
|
679
733
|
result += ` ${renderExpr(fzindex, env)}`;
|
|
680
734
|
|
|
681
|
-
if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options))
|
|
682
|
-
result += ` COMMENT '${getHanaComment(elm)}'`;
|
|
683
|
-
|
|
684
735
|
// (table) elements can only have a @sql.append
|
|
685
736
|
const { back } = getSqlSnippets(options, elm);
|
|
686
737
|
|
|
687
|
-
if (back !== '')
|
|
738
|
+
if (back !== '') // Needs to be rendered before the COMMENT
|
|
688
739
|
result += back;
|
|
689
740
|
|
|
741
|
+
if (options.toSql.dialect === 'hana' && hasHanaComment(elm, options))
|
|
742
|
+
result += ` COMMENT '${getHanaComment(elm)}'`;
|
|
743
|
+
|
|
690
744
|
return result;
|
|
691
745
|
}
|
|
692
746
|
|
|
@@ -751,7 +805,7 @@ function toSqlDdl(csn, options) {
|
|
|
751
805
|
// This also affects renderIndexes
|
|
752
806
|
tc = tc.hana;
|
|
753
807
|
if (!tc)
|
|
754
|
-
throw new
|
|
808
|
+
throw new ModelError('Expecting a HANA technical configuration');
|
|
755
809
|
|
|
756
810
|
if (tc.tableSuffix) {
|
|
757
811
|
// Although we could just render the whole bandwurm as one stream of tokens, the
|
|
@@ -820,7 +874,7 @@ function toSqlDdl(csn, options) {
|
|
|
820
874
|
const i = index.indexOf('index');
|
|
821
875
|
const j = index.indexOf('(');
|
|
822
876
|
if (i > index.length - 2 || !index[i + 1].ref || j < i || j > index.length - 2)
|
|
823
|
-
throw new
|
|
877
|
+
throw new ModelError(`Unexpected form of index: "${index}"`);
|
|
824
878
|
|
|
825
879
|
let indexName = renderArtifactName(`${artifactName}.${index[i + 1].ref}`);
|
|
826
880
|
if (options.toSql.names === 'plain')
|
|
@@ -876,7 +930,7 @@ function toSqlDdl(csn, options) {
|
|
|
876
930
|
|
|
877
931
|
// Sanity check
|
|
878
932
|
if (!source.ref)
|
|
879
|
-
throw new
|
|
933
|
+
throw new ModelError(`Expecting ref in ${JSON.stringify(source)}`);
|
|
880
934
|
|
|
881
935
|
return renderAbsolutePathWithAlias(artifactName, source, env);
|
|
882
936
|
}
|
|
@@ -918,7 +972,7 @@ function toSqlDdl(csn, options) {
|
|
|
918
972
|
function renderAbsolutePathWithAlias(artifactName, path, env) {
|
|
919
973
|
// This actually can't happen anymore because assoc2joins should have taken care of it
|
|
920
974
|
if (path.ref[0].where)
|
|
921
|
-
throw new
|
|
975
|
+
throw new ModelError(`"${artifactName}": Filters in FROM are not supported for conversion to SQL`);
|
|
922
976
|
|
|
923
977
|
|
|
924
978
|
// SQL needs a ':' after path.ref[0] to separate associations
|
|
@@ -956,7 +1010,7 @@ function toSqlDdl(csn, options) {
|
|
|
956
1010
|
function renderAbsolutePath(path, sep, env) {
|
|
957
1011
|
// Sanity checks
|
|
958
1012
|
if (!path.ref)
|
|
959
|
-
throw new
|
|
1013
|
+
throw new ModelError(`Expecting ref in path: ${JSON.stringify(path)}`);
|
|
960
1014
|
|
|
961
1015
|
// Determine the absolute name of the first artifact on the path (before any associations or element traversals)
|
|
962
1016
|
const firstArtifactName = path.ref[0].id || path.ref[0];
|
|
@@ -973,7 +1027,7 @@ function toSqlDdl(csn, options) {
|
|
|
973
1027
|
if (ref && ref.params) {
|
|
974
1028
|
result += `(${renderArgs(path.ref[0] || {}, '=>', env, syntax)})`;
|
|
975
1029
|
}
|
|
976
|
-
else if (
|
|
1030
|
+
else if (syntax === 'udf') {
|
|
977
1031
|
// if syntax is user defined function, render empty argument list
|
|
978
1032
|
// CV without parameters is called as simple view
|
|
979
1033
|
result += '()';
|
|
@@ -1004,15 +1058,15 @@ function toSqlDdl(csn, options) {
|
|
|
1004
1058
|
const args = node.args ? node.args : {};
|
|
1005
1059
|
// Positional arguments
|
|
1006
1060
|
if (Array.isArray(args))
|
|
1007
|
-
return args.map(arg => renderExpr(arg, env)).join(', ');
|
|
1061
|
+
return args.map(arg => renderExpr(arg, env, true, false, true)).join(', ');
|
|
1008
1062
|
|
|
1009
1063
|
// Named arguments (object/dict)
|
|
1010
1064
|
else if (typeof args === 'object')
|
|
1011
1065
|
// if this is a function param which is not a reference to the model, we must not quote it
|
|
1012
|
-
return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env)}`).join(', ');
|
|
1066
|
+
return Object.keys(args).map(key => `${node.func ? key : decorateParameter(key, syntax)} ${sep} ${renderExpr(args[key], env, true, false, true)}`).join(', ');
|
|
1013
1067
|
|
|
1014
1068
|
|
|
1015
|
-
throw new
|
|
1069
|
+
throw new ModelError(`Unknown args: ${JSON.stringify(args)}`);
|
|
1016
1070
|
|
|
1017
1071
|
|
|
1018
1072
|
/**
|
|
@@ -1166,7 +1220,7 @@ function toSqlDdl(csn, options) {
|
|
|
1166
1220
|
}
|
|
1167
1221
|
// Otherwise must have a SELECT
|
|
1168
1222
|
else if (!query.SELECT) {
|
|
1169
|
-
throw new
|
|
1223
|
+
throw new ModelError(`Unexpected query operation ${JSON.stringify(query)}`);
|
|
1170
1224
|
}
|
|
1171
1225
|
const select = query.SELECT;
|
|
1172
1226
|
const childEnv = increaseIndent(env);
|
|
@@ -1182,7 +1236,7 @@ function toSqlDdl(csn, options) {
|
|
|
1182
1236
|
result += `\n${env.indent}WHERE ${renderExpr(select.where, env, true, true)}`;
|
|
1183
1237
|
|
|
1184
1238
|
if (select.groupBy)
|
|
1185
|
-
result += `\n${env.indent}GROUP BY ${select.groupBy.map(expr => renderExpr(expr, env)).join(', ')}`;
|
|
1239
|
+
result += `\n${env.indent}GROUP BY ${select.groupBy.map(expr => renderExpr(expr, env, true, false, true)).join(', ')}`;
|
|
1186
1240
|
|
|
1187
1241
|
if (select.having)
|
|
1188
1242
|
result += `\n${env.indent}HAVING ${renderExpr(select.having, env, true, true)}`;
|
|
@@ -1233,7 +1287,7 @@ function toSqlDdl(csn, options) {
|
|
|
1233
1287
|
* @returns {string} Rendered ORDER BY entry
|
|
1234
1288
|
*/
|
|
1235
1289
|
function renderOrderByEntry(entry, env) {
|
|
1236
|
-
let result = renderExpr(entry, env);
|
|
1290
|
+
let result = renderExpr(entry, env, true, false, true);
|
|
1237
1291
|
if (entry.sort)
|
|
1238
1292
|
result += ` ${entry.sort.toUpperCase()}`;
|
|
1239
1293
|
|
|
@@ -1257,7 +1311,7 @@ function toSqlDdl(csn, options) {
|
|
|
1257
1311
|
// Anonymous structured type: Not supported with SQL (but shouldn't happen anyway after forHana flattened them)
|
|
1258
1312
|
if (!elm.type) {
|
|
1259
1313
|
if (!elm.elements)
|
|
1260
|
-
throw new
|
|
1314
|
+
throw new ModelError(`Missing type of: ${elementName}`);
|
|
1261
1315
|
|
|
1262
1316
|
// TODO: Signal is not covered by tests + better location
|
|
1263
1317
|
error(null, [ 'definitions', artifactName, 'elements', elementName ],
|
|
@@ -1280,7 +1334,7 @@ function toSqlDdl(csn, options) {
|
|
|
1280
1334
|
result += renderBuiltinType(elm.type);
|
|
1281
1335
|
}
|
|
1282
1336
|
else {
|
|
1283
|
-
throw new
|
|
1337
|
+
throw new ModelError(`Unexpected non-primitive type of: ${artifactName}.${elementName}`);
|
|
1284
1338
|
}
|
|
1285
1339
|
result += renderTypeParameters(elm);
|
|
1286
1340
|
return result;
|
|
@@ -1309,7 +1363,7 @@ function toSqlDdl(csn, options) {
|
|
|
1309
1363
|
* Render the nullability of an element or parameter (can be unset, true, or false)
|
|
1310
1364
|
*
|
|
1311
1365
|
* @param {object} obj Object to render for
|
|
1312
|
-
* @param {boolean} treatKeyAsNotNull
|
|
1366
|
+
* @param {boolean} treatKeyAsNotNull Whether to render KEY as not null
|
|
1313
1367
|
* @returns {string} NULL/NOT NULL or ''
|
|
1314
1368
|
*/
|
|
1315
1369
|
function renderNullability(obj, treatKeyAsNotNull = false) {
|
|
@@ -1356,18 +1410,20 @@ function toSqlDdl(csn, options) {
|
|
|
1356
1410
|
* @todo Reuse this with toCdl
|
|
1357
1411
|
* @param {Array|object|string} expr Expression to render
|
|
1358
1412
|
* @param {object} env Render environment
|
|
1359
|
-
* @param {boolean} inline
|
|
1360
|
-
* @param {boolean} nestedExpr
|
|
1413
|
+
* @param {boolean} [inline=true] Whether to render the expression inline
|
|
1414
|
+
* @param {boolean} [nestedExpr=false] Whether to treat the expression as nested
|
|
1415
|
+
* @param {boolean} [alwaysRenderCast=false] Whether to _always_ render SQL-style casts, even if `nestedExpr === false`.
|
|
1416
|
+
* Note: This is a hack for casts() inside groupBy.
|
|
1361
1417
|
* @returns {string} Rendered expression
|
|
1362
1418
|
*/
|
|
1363
|
-
function renderExpr(expr, env, inline = true, nestedExpr = false) {
|
|
1419
|
+
function renderExpr(expr, env, inline = true, nestedExpr = false, alwaysRenderCast = false) {
|
|
1364
1420
|
// Compound expression
|
|
1365
1421
|
if (Array.isArray(expr)) {
|
|
1366
1422
|
const tokens = expr.map(item => renderExpr(item, env, inline, nestedExpr));
|
|
1367
1423
|
return beautifyExprArray(tokens);
|
|
1368
1424
|
}
|
|
1369
1425
|
else if (typeof expr === 'object' && expr !== null) {
|
|
1370
|
-
if (nestedExpr && expr.cast && expr.cast.type)
|
|
1426
|
+
if ((nestedExpr || alwaysRenderCast) && expr.cast && expr.cast.type)
|
|
1371
1427
|
return renderExplicitTypeCast(expr, renderExprObject(expr));
|
|
1372
1428
|
return renderExprObject(expr);
|
|
1373
1429
|
}
|
|
@@ -1380,6 +1436,7 @@ function toSqlDdl(csn, options) {
|
|
|
1380
1436
|
/**
|
|
1381
1437
|
* Various special cases represented as objects
|
|
1382
1438
|
*
|
|
1439
|
+
* @param {object} x Expression
|
|
1383
1440
|
* @returns {string} String representation of the expression
|
|
1384
1441
|
*/
|
|
1385
1442
|
function renderExprObject(x) {
|
|
@@ -1425,7 +1482,7 @@ function toSqlDdl(csn, options) {
|
|
|
1425
1482
|
return `${renderQuery('<union>', x, increaseIndent(env))}`;
|
|
1426
1483
|
}
|
|
1427
1484
|
|
|
1428
|
-
throw new
|
|
1485
|
+
throw new ModelError(`Unknown expression: ${JSON.stringify(x)}`);
|
|
1429
1486
|
}
|
|
1430
1487
|
|
|
1431
1488
|
function renderWindowFunction(funcName, node, fctEnv) {
|
|
@@ -1465,7 +1522,7 @@ function toSqlDdl(csn, options) {
|
|
|
1465
1522
|
|
|
1466
1523
|
// otherwise fall through to
|
|
1467
1524
|
default:
|
|
1468
|
-
throw new
|
|
1525
|
+
throw new ModelError(`Unknown literal or type: ${JSON.stringify(x)}`);
|
|
1469
1526
|
}
|
|
1470
1527
|
}
|
|
1471
1528
|
|
|
@@ -1628,7 +1685,7 @@ function toSqlDdl(csn, options) {
|
|
|
1628
1685
|
else if (typeof s === 'object') {
|
|
1629
1686
|
// Sanity check
|
|
1630
1687
|
if (!s.func && !s.id)
|
|
1631
|
-
throw new
|
|
1688
|
+
throw new ModelError(`Unknown path step object: ${JSON.stringify(s)}`);
|
|
1632
1689
|
|
|
1633
1690
|
// Not really a path step but an object-like function call
|
|
1634
1691
|
if (s.func)
|
|
@@ -1648,7 +1705,7 @@ function toSqlDdl(csn, options) {
|
|
|
1648
1705
|
return result;
|
|
1649
1706
|
}
|
|
1650
1707
|
|
|
1651
|
-
throw new
|
|
1708
|
+
throw new ModelError(`Unknown path step: ${JSON.stringify(s)}`);
|
|
1652
1709
|
}
|
|
1653
1710
|
}
|
|
1654
1711
|
|
|
@@ -19,8 +19,6 @@ const {
|
|
|
19
19
|
} = require('../../model/csnUtils');
|
|
20
20
|
|
|
21
21
|
const { implicitAs } = require('../../model/csnRefs');
|
|
22
|
-
const { isBetaEnabled } = require('../../base/model');
|
|
23
|
-
|
|
24
22
|
|
|
25
23
|
/**
|
|
26
24
|
* Render the given function
|
|
@@ -38,7 +36,7 @@ function renderFunc( funcName, node, dialect, renderArgs) {
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
/**
|
|
41
|
-
* Checks
|
|
39
|
+
* Checks whether the given function is to be rendered without parentheses
|
|
42
40
|
*
|
|
43
41
|
* @param {object} node Content of the function
|
|
44
42
|
* @param {string} dialect One of 'hana', 'cap' or 'sqlite' - only 'hana' is relevant atm
|
|
@@ -332,7 +330,7 @@ function addIntermediateContexts(csn, killList) {
|
|
|
332
330
|
}
|
|
333
331
|
|
|
334
332
|
/**
|
|
335
|
-
* Check
|
|
333
|
+
* Check whether the given artifact or element has a comment that needs to be rendered.
|
|
336
334
|
* Things annotated with @cds.persistence.journal (for HANA SQL), should not get a comment.
|
|
337
335
|
*
|
|
338
336
|
* @param {CSN.Artifact} obj
|
|
@@ -364,13 +362,10 @@ function getHanaComment(obj) {
|
|
|
364
362
|
* @returns {object} object with .front and .back
|
|
365
363
|
*/
|
|
366
364
|
function getSqlSnippets(options, obj) {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
|
|
365
|
+
const front = obj['@sql.prepend'] ? `${obj['@sql.prepend']} ` : '';
|
|
366
|
+
const back = obj['@sql.append'] ? ` ${obj['@sql.append']}` : '';
|
|
370
367
|
|
|
371
|
-
|
|
372
|
-
}
|
|
373
|
-
return { front: '', back: '' };
|
|
368
|
+
return { front, back };
|
|
374
369
|
}
|
|
375
370
|
|
|
376
371
|
/**
|
package/lib/render/utils/sql.js
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
|
|
5
5
|
const { getResultingName } = require('../../model/csnUtils');
|
|
6
6
|
const { smartId, delimitedId } = require('../../sql-identifier');
|
|
7
|
+
const { ModelError } = require('../../base/error');
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Render a given referential constraint as part of a SQL CREATE TABLE statement, or as .hdbconstraint artefact.
|
|
10
11
|
*
|
|
11
12
|
* @param {CSN.ReferentialConstraint} constraint Content of the constraint
|
|
12
13
|
* @param {string} indent Indent to render the SQL with
|
|
13
|
-
* @param {boolean} toUpperCase
|
|
14
|
+
* @param {boolean} toUpperCase Whether to uppercase the identifier
|
|
14
15
|
* @param {CSN.Model} csn CSN
|
|
15
16
|
* @param {CSN.Options} options is needed for the naming mode and the sql dialect
|
|
16
17
|
* @param {boolean} [alterConstraint=false] whether the constraint should be rendered as part of an ALTER TABLE statement
|
|
@@ -121,7 +122,7 @@ function getIdentifierUtils(options) {
|
|
|
121
122
|
function prepareIdentifier(name) {
|
|
122
123
|
// Sanity check
|
|
123
124
|
if (options.toSql.dialect === 'sqlite' && options.toSql.names !== 'plain')
|
|
124
|
-
throw new
|
|
125
|
+
throw new ModelError(`Not expecting ${options.toSql.names} names for 'sqlite' dialect`);
|
|
125
126
|
|
|
126
127
|
|
|
127
128
|
switch (options.toSql.names) {
|
|
@@ -132,7 +133,7 @@ function getIdentifierUtils(options) {
|
|
|
132
133
|
case 'hdbcds':
|
|
133
134
|
return name;
|
|
134
135
|
default:
|
|
135
|
-
throw new
|
|
136
|
+
throw new ModelError(`No matching rendering found for naming mode ${options.toSql.names}`);
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
}
|