@sap/cds 6.8.4 → 7.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 +58 -5
- package/README.md +0 -1
- package/bin/cds-serve.js +50 -3
- package/bin/deploy/to-hana.js +1 -0
- package/bin/serve.js +16 -20
- package/lib/auth/basic-auth.js +6 -4
- package/lib/auth/index.js +4 -3
- package/lib/auth/jwt-auth.js +2 -5
- package/lib/compile/cds-compile.js +34 -89
- package/lib/compile/cdsc.js +11 -0
- package/lib/compile/etc/properties.js +2 -2
- package/lib/compile/for/lean_drafts.js +36 -69
- package/lib/compile/for/nodejs.js +2 -1
- package/lib/compile/load.js +1 -1
- package/lib/compile/minify.js +2 -0
- package/lib/compile/to/csn.js +74 -0
- package/{bin/build/provider/hana/2tabledata.js → lib/compile/to/hdbtabledata.js} +4 -6
- package/lib/compile/to/json.js +1 -1
- package/lib/compile/to/sql.js +8 -6
- package/lib/dbs/cds-deploy.js +174 -114
- package/lib/env/cds-env.js +64 -79
- package/lib/env/cds-requires.js +11 -28
- package/lib/env/defaults.js +13 -3
- package/lib/env/plugins.js +1 -12
- package/lib/env/presets.js +25 -21
- package/lib/index.js +121 -147
- package/lib/{core/reflect.js → linked/models.js} +2 -2
- package/lib/{core/infer.js → linked/queries.js} +2 -0
- package/lib/{core/index.js → linked/types.js} +2 -1
- package/lib/log/cds-error.js +13 -7
- package/lib/log/format/cf.js +1 -1
- package/lib/plugins.js +49 -0
- package/lib/ql/Query.js +0 -9
- package/lib/ql/STREAM.js +0 -1
- package/lib/req/context.js +2 -7
- package/lib/req/request.js +6 -2
- package/lib/req/response.js +23 -10
- package/lib/srv/middlewares/ctx-model.js +1 -1
- package/lib/srv/middlewares/errors.js +1 -1
- package/lib/srv/protocols/_legacy.js +1 -0
- package/lib/srv/protocols/graphql.js +7 -16
- package/lib/srv/protocols/index.js +59 -45
- package/lib/srv/protocols/odata-v2-proxy.js +2 -70
- package/lib/srv/srv-api.js +9 -3
- package/lib/srv/srv-dispatch.js +12 -9
- package/lib/srv/srv-models.js +4 -21
- package/lib/srv/srv-tx.js +15 -12
- package/lib/utils/cds-test.js +14 -9
- package/lib/utils/cds-utils.js +2 -12
- package/lib/utils/check-version.js +17 -0
- package/{bin/build → lib/utils}/csv-reader.js +23 -24
- package/libx/_runtime/auth/index.js +27 -23
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +15 -72
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +0 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +33 -63
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +14 -18
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +15 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +5 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +37 -40
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/updateToCQN.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +101 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js +5 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +9 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +15 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +4 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +5 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +1 -123
- package/libx/_runtime/cds-services/services/Service.js +79 -107
- package/libx/_runtime/cds-services/services/utils/columns.js +23 -19
- package/libx/_runtime/cds-services/services/utils/compareJson.js +11 -1
- package/libx/_runtime/cds-services/services/utils/differ.js +7 -2
- package/libx/_runtime/cds-services/util/assert.js +65 -2
- package/libx/_runtime/common/composition/data.js +1 -0
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/restrict.js +5 -10
- package/libx/_runtime/common/generic/auth/restrictions.js +40 -0
- package/libx/_runtime/common/generic/auth/utils.js +1 -2
- package/libx/_runtime/common/generic/crud.js +32 -16
- package/libx/_runtime/common/generic/etag.js +133 -104
- package/libx/_runtime/common/generic/input.js +6 -21
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/stream.js +52 -0
- package/libx/_runtime/common/generic/temporal.js +25 -8
- package/libx/_runtime/common/i18n/messages.properties +0 -2
- package/libx/_runtime/common/utils/cqn.js +1 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +5 -2
- package/libx/_runtime/common/utils/csn.js +0 -51
- package/libx/_runtime/common/utils/etag.js +30 -0
- package/libx/_runtime/common/utils/keys.js +1 -1
- package/libx/_runtime/common/utils/normalizeTimestamp.js +25 -0
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +2 -1
- package/libx/_runtime/common/utils/rewriteAsterisks.js +6 -4
- package/libx/_runtime/common/utils/search2cqn4sql.js +12 -16
- package/libx/_runtime/common/utils/stream.js +140 -0
- package/libx/_runtime/common/utils/streamProp.js +29 -12
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +0 -2
- package/libx/_runtime/db/generic/index.js +0 -2
- package/libx/_runtime/db/query/delete.js +2 -2
- package/libx/_runtime/db/query/insert.js +2 -2
- package/libx/_runtime/db/query/read.js +2 -2
- package/libx/_runtime/db/query/run.js +2 -2
- package/libx/_runtime/db/query/update.js +2 -2
- package/libx/_runtime/db/sql-builder/BaseBuilder.js +0 -6
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +23 -12
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +18 -6
- package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -0
- package/libx/_runtime/db/sql-builder/SelectBuilder.js +3 -7
- package/libx/_runtime/db/sql-builder/UpsertBuilder.js +1 -0
- package/libx/_runtime/db/utils/normalizeTimeData.js +7 -3
- package/libx/_runtime/fiori/draft.js +2 -0
- package/libx/_runtime/fiori/generic/activate.js +8 -9
- package/libx/_runtime/fiori/generic/before.js +30 -20
- package/libx/_runtime/fiori/generic/cancel.js +5 -3
- package/libx/_runtime/fiori/generic/delete.js +5 -3
- package/libx/_runtime/fiori/generic/edit.js +7 -7
- package/libx/_runtime/fiori/generic/index.js +10 -16
- package/libx/_runtime/fiori/generic/new.js +5 -3
- package/libx/_runtime/fiori/generic/patch.js +11 -8
- package/libx/_runtime/fiori/generic/prepare.js +13 -6
- package/libx/_runtime/fiori/generic/read.js +12 -6
- package/libx/_runtime/fiori/lean-draft.js +207 -152
- package/libx/_runtime/fiori/utils/delete.js +10 -5
- package/libx/_runtime/fiori/utils/req.js +17 -5
- package/libx/_runtime/fiori/utils/stream.js +36 -0
- package/libx/_runtime/hana/Service.js +12 -9
- package/libx/_runtime/hana/conversion.js +10 -15
- package/libx/_runtime/hana/driver.js +2 -0
- package/libx/_runtime/hana/execute.js +28 -6
- package/libx/_runtime/hana/pool.js +36 -122
- package/libx/_runtime/hana/search2cqn4sql.js +34 -36
- package/libx/_runtime/messaging/enterprise-messaging-utils/getTenantInfo.js +2 -6
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +3 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +10 -58
- package/libx/_runtime/messaging/outbox/utils.js +1 -1
- package/libx/_runtime/remote/Service.js +20 -1
- package/libx/_runtime/remote/utils/client.js +3 -5
- package/libx/_runtime/sqlite/Service.js +4 -6
- package/libx/_runtime/sqlite/conversion.js +3 -13
- package/libx/_runtime/sqlite/customBuilder/CustomFunctionBuilder.js +9 -6
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +6 -1
- package/libx/_runtime/sqlite/execute.js +5 -16
- package/libx/odata/afterburner.js +22 -6
- package/libx/odata/grammar.pegjs +6 -1
- package/libx/odata/parser.js +1 -1
- package/libx/rest/RestAdapter.js +16 -9
- package/libx/rest/RestRequest.js +1 -1
- package/libx/rest/middleware/input.js +2 -1
- package/libx/rest/middleware/operation.js +1 -0
- package/libx/rest/middleware/parse.js +3 -2
- package/libx/rest/middleware/payload.js +9 -8
- package/libx/rest/middleware/read.js +1 -0
- package/package.json +9 -16
- package/app/fiori/preview.js +0 -270
- package/app/fiori/routes.js +0 -59
- package/bin/build/buildTaskEngine.js +0 -360
- package/bin/build/buildTaskFactory.js +0 -283
- package/bin/build/buildTaskHandler.js +0 -241
- package/bin/build/buildTaskProvider.js +0 -22
- package/bin/build/buildTaskProviderFactory.js +0 -175
- package/bin/build/cds.js +0 -5
- package/bin/build/constants.js +0 -66
- package/bin/build/index.js +0 -58
- package/bin/build/provider/buildTaskHandlerEdmx.js +0 -82
- package/bin/build/provider/buildTaskHandlerFeatureToggles.js +0 -131
- package/bin/build/provider/buildTaskHandlerInternal.js +0 -254
- package/bin/build/provider/buildTaskProviderInternal.js +0 -383
- package/bin/build/provider/fiori/index.js +0 -171
- package/bin/build/provider/hana/2migration.js +0 -179
- package/bin/build/provider/hana/index.js +0 -505
- package/bin/build/provider/hana/migrationtable.js +0 -472
- package/bin/build/provider/hana/template/.hdiconfig-haas +0 -163
- package/bin/build/provider/hana/template/.hdiconfig-hanacloud +0 -137
- package/bin/build/provider/hana/template/.hdinamespace +0 -4
- package/bin/build/provider/hana/template/package.json +0 -12
- package/bin/build/provider/hana/template/undeploy.json +0 -5
- package/bin/build/provider/java/index.js +0 -111
- package/bin/build/provider/java-cf/index.js +0 -1
- package/bin/build/provider/mtx/index.js +0 -268
- package/bin/build/provider/mtx/resourcesTarBuilder.js +0 -95
- package/bin/build/provider/mtx-extension/index.js +0 -131
- package/bin/build/provider/mtx-sidecar/index.js +0 -137
- package/bin/build/provider/node-cf/index.js +0 -1
- package/bin/build/provider/nodejs/index.js +0 -192
- package/bin/build/util.js +0 -299
- package/bin/cds.js +0 -125
- package/bin/deploy/to-hana/cfUtil.js +0 -355
- package/bin/deploy/to-hana/gitUtil.js +0 -57
- package/bin/deploy/to-hana/hana.js +0 -306
- package/bin/deploy/to-hana/hdiDeployUtil.js +0 -153
- package/bin/deploy/to-hana/index.js +0 -16
- package/bin/deploy/to-hana/mtaUtil.js +0 -170
- package/bin/mtx/in-cds.js +0 -17
- package/bin/plugins.js +0 -32
- package/bin/run.js +0 -24
- package/bin/utils/log.js +0 -24
- package/bin/version.js +0 -178
- package/libx/_runtime/audit/Service.js +0 -222
- package/libx/_runtime/audit/generic/personal/access.js +0 -61
- package/libx/_runtime/audit/generic/personal/index.js +0 -56
- package/libx/_runtime/audit/generic/personal/modification.js +0 -132
- package/libx/_runtime/audit/generic/personal/utils.js +0 -186
- package/libx/_runtime/audit/utils/log.js +0 -23
- package/libx/_runtime/audit/utils/v2.js +0 -176
- package/libx/_runtime/db/data-conversion/timestamp.js +0 -9
- package/libx/_runtime/db/generic/integrity.js +0 -455
- package/srv/audit-log.cds +0 -87
- package/srv/mtx.cds +0 -2
- package/srv/mtx.js +0 -8
- /package/lib/{core → linked}/classes.js +0 -0
- /package/lib/{core → linked}/entities.js +0 -0
|
@@ -29,7 +29,7 @@ const _validate = (activeResult, draftResult, req, IsActiveEntity) => {
|
|
|
29
29
|
(IsActiveEntity === true && activeResult.length === 0) ||
|
|
30
30
|
(IsActiveEntity === false && draftResult.length === 0)
|
|
31
31
|
) {
|
|
32
|
-
req.reject(404)
|
|
32
|
+
req.reject(req._etagValidationClause ? 412 : 404)
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -46,10 +46,15 @@ const deleteDraft = async (req, srv, includingActive = false) => {
|
|
|
46
46
|
// Intentional?
|
|
47
47
|
const deleteActive = keys.IsActiveEntity !== false
|
|
48
48
|
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
49
|
+
const selectActive = _getSelectCQN(req, keys)
|
|
50
|
+
const selectDraft = _getDraftSelectCQN(req, keys)
|
|
51
|
+
|
|
52
|
+
if (req._etagValidationClause) {
|
|
53
|
+
if (keys.IsActiveEntity) selectActive.where(req._etagValidationClause)
|
|
54
|
+
else selectDraft.where(req._etagValidationClause)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const [activeResult, draftResult] = await Promise.all([dbtx.run(selectActive), dbtx.run(selectDraft)])
|
|
53
58
|
|
|
54
59
|
_validate(activeResult, draftResult, req, deleteActive)
|
|
55
60
|
|
|
@@ -1,9 +1,21 @@
|
|
|
1
|
-
const
|
|
2
|
-
// REVISIT: get rid of getUriInfo
|
|
3
|
-
if (!req.getUriInfo) return
|
|
1
|
+
const _ref2name = ref => (ref.id || ref).replace(/_drafts$/, '')
|
|
4
2
|
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
const isNavigationToMany = (req, model) => {
|
|
4
|
+
// only one segment -> false
|
|
5
|
+
if (req.subject.ref.length < 2) return
|
|
6
|
+
|
|
7
|
+
// last segment has it's own where -> false
|
|
8
|
+
if (req.subject.ref[req.subject.ref.length - 1].where) return
|
|
9
|
+
|
|
10
|
+
// determine the csn element of the last navigation and return it's is2many property
|
|
11
|
+
let cur
|
|
12
|
+
for (let i = 0; i < req.subject.ref.length - 1; i++) {
|
|
13
|
+
cur = cur
|
|
14
|
+
? model.definitions[cur.elements[_ref2name(req.subject.ref[i])].target]
|
|
15
|
+
: model.definitions[_ref2name(req.subject.ref[i])]
|
|
16
|
+
}
|
|
17
|
+
const last = cur.elements[_ref2name(req.subject.ref[req.subject.ref.length - 1])]
|
|
18
|
+
return last.is2many
|
|
7
19
|
}
|
|
8
20
|
|
|
9
21
|
module.exports = {
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const { ensureDraftsSuffix } = require('./handler')
|
|
2
|
+
const { removeIsActiveEntityRecursively, isActiveEntityRequested } = require('./where')
|
|
3
|
+
|
|
4
|
+
const _adaptSubSelectsDraft = select => {
|
|
5
|
+
if (select.SELECT.from.ref) {
|
|
6
|
+
const index = select.SELECT.from.ref.length - 1
|
|
7
|
+
select.SELECT.from.ref[index] = ensureDraftsSuffix(select.SELECT.from.ref[index])
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (select.SELECT.where) {
|
|
11
|
+
for (let i = 0; i < select.SELECT.where.length; i++) {
|
|
12
|
+
const element = select.SELECT.where[i]
|
|
13
|
+
if (element.SELECT) {
|
|
14
|
+
_adaptSubSelectsDraft(element)
|
|
15
|
+
} else if (element.xpr) {
|
|
16
|
+
_adaptSubSelectsDraft({ SELECT: { from: {}, where: element.xpr } })
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const adaptStreamCQN = (cqn, isDraft = false) => {
|
|
23
|
+
let draft = isDraft
|
|
24
|
+
if (!draft) {
|
|
25
|
+
draft = !isActiveEntityRequested(cqn.SELECT.where)
|
|
26
|
+
const ref = cqn.SELECT.from?.ref
|
|
27
|
+
if (!draft && ref) draft = !isActiveEntityRequested(ref[ref.length - 1].where)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (draft) _adaptSubSelectsDraft(cqn)
|
|
31
|
+
cqn.SELECT.where = removeIsActiveEntityRecursively(cqn.SELECT.where)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = {
|
|
35
|
+
adaptStreamCQN
|
|
36
|
+
}
|
|
@@ -57,17 +57,20 @@ class HanaDatabase extends DatabaseService {
|
|
|
57
57
|
*/
|
|
58
58
|
this.on('BEGIN', function () {
|
|
59
59
|
LOG._debug && LOG.debug(coloredTxCommands['BEGIN'])
|
|
60
|
+
|
|
60
61
|
this.dbc.setAutoCommit(false)
|
|
62
|
+
|
|
61
63
|
return 'dummy'
|
|
62
64
|
})
|
|
63
65
|
|
|
64
|
-
// REVISIT: register only if needed?
|
|
65
|
-
this.before('COMMIT', this._integrity.performCheck)
|
|
66
|
-
|
|
67
66
|
this.on(['COMMIT', 'ROLLBACK'], function (req) {
|
|
68
67
|
LOG._debug && LOG.debug(coloredTxCommands[req.event])
|
|
68
|
+
|
|
69
|
+
// REVISIT: better?
|
|
70
|
+
this.dbc._closed = true
|
|
71
|
+
|
|
69
72
|
return new Promise((resolve, reject) => {
|
|
70
|
-
this.dbc[req.event.toLowerCase()](
|
|
73
|
+
this.dbc[req.event.toLowerCase()](err => {
|
|
71
74
|
try {
|
|
72
75
|
this.dbc.setAutoCommit(true)
|
|
73
76
|
} catch (e) {
|
|
@@ -97,9 +100,6 @@ class HanaDatabase extends DatabaseService {
|
|
|
97
100
|
|
|
98
101
|
this.before('READ', '*', localized) // > has to run after rewrite
|
|
99
102
|
this.before('READ', '*', this._virtual)
|
|
100
|
-
|
|
101
|
-
// REVISIT: get data to be deleted for integrity check
|
|
102
|
-
this.before('DELETE', '*', this._integrity.beforeDelete)
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
_registerOnHandlers() {
|
|
@@ -124,8 +124,7 @@ class HanaDatabase extends DatabaseService {
|
|
|
124
124
|
*/
|
|
125
125
|
// eslint-disable-next-line complexity
|
|
126
126
|
async acquire(arg) {
|
|
127
|
-
|
|
128
|
-
const tenant = arg && (typeof arg === 'string' ? arg : arg.tenant || (arg.user && arg.user.tenant))
|
|
127
|
+
const tenant = arg && (typeof arg === 'string' ? arg : arg.tenant)
|
|
129
128
|
const dbc = await pool.acquire(tenant, this)
|
|
130
129
|
|
|
131
130
|
if (arg && typeof arg !== 'string') {
|
|
@@ -152,6 +151,10 @@ class HanaDatabase extends DatabaseService {
|
|
|
152
151
|
}
|
|
153
152
|
|
|
154
153
|
dbc._tenant = tenant
|
|
154
|
+
|
|
155
|
+
// REVISIT: better?
|
|
156
|
+
dbc._closed = false
|
|
157
|
+
|
|
155
158
|
return dbc
|
|
156
159
|
}
|
|
157
160
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
const cds = require('
|
|
2
|
-
|
|
1
|
+
const cds = require('../../../lib')
|
|
3
2
|
const driver = require('./driver')
|
|
4
3
|
const isHdb = driver?.name === 'hdb'
|
|
5
4
|
|
|
@@ -20,11 +19,17 @@ const convertInt64ToString = int64 => {
|
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
const convertToISO = element => {
|
|
23
|
-
if (element)
|
|
24
|
-
|
|
22
|
+
if (!element) return null
|
|
23
|
+
|
|
24
|
+
if (cds.env.features.precise_timestamps) {
|
|
25
|
+
const dateTime = element.slice(0, 19).replace(' ', 'T')
|
|
26
|
+
let millis = element.slice(20)
|
|
27
|
+
if (millis.at(-1) === 'Z') millis = millis.slice(0, -1)
|
|
28
|
+
millis = millis.slice(0, 7).padEnd(7, '0')
|
|
29
|
+
return dateTime + '.' + millis + 'Z'
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
return
|
|
32
|
+
return new Date(element + 'Z').toISOString()
|
|
28
33
|
}
|
|
29
34
|
|
|
30
35
|
const convertToISONoMillis = element => {
|
|
@@ -62,16 +67,6 @@ const HANA_TYPE_CONVERSION_MAP = new Map([
|
|
|
62
67
|
['cds.hana.CLOB', convertToString]
|
|
63
68
|
])
|
|
64
69
|
|
|
65
|
-
if (cds.env.features.bigjs) {
|
|
66
|
-
// eslint-disable-next-line cds/no-missing-dependencies -- needs to be added by app dev
|
|
67
|
-
const Big = require('big.js')
|
|
68
|
-
const convertToBig = value => new Big(value)
|
|
69
|
-
|
|
70
|
-
HANA_TYPE_CONVERSION_MAP.set('cds.Integer64', convertToBig)
|
|
71
|
-
HANA_TYPE_CONVERSION_MAP.set('cds.Int64', convertToBig)
|
|
72
|
-
HANA_TYPE_CONVERSION_MAP.set('cds.Decimal', convertToBig)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
70
|
module.exports = {
|
|
76
71
|
HANA_TYPE_CONVERSION_MAP
|
|
77
72
|
}
|
|
@@ -100,6 +100,9 @@ function _hcGetResultForProcedure(stmt, resultSet, outParameters) {
|
|
|
100
100
|
|
|
101
101
|
function _getProcedureMetadata(procedureName, dbc) {
|
|
102
102
|
return new Promise((resolve, reject) => {
|
|
103
|
+
// REVISIT: better?
|
|
104
|
+
if (dbc._closed) return reject(new Error('Transaction is already closed'))
|
|
105
|
+
|
|
103
106
|
dbc.exec(
|
|
104
107
|
`SELECT PARAMETER_NAME FROM SYS.PROCEDURE_PARAMETERS WHERE SCHEMA_NAME = CURRENT_SCHEMA AND PROCEDURE_NAME = '${procedureName}' AND PARAMETER_TYPE IN ('OUT', 'INOUT') ORDER BY POSITION`,
|
|
105
108
|
(err, res) => {
|
|
@@ -111,6 +114,9 @@ function _getProcedureMetadata(procedureName, dbc) {
|
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
117
|
+
// REVISIT: better?
|
|
118
|
+
if (dbc._closed) return reject(new Error('Transaction is already closed'))
|
|
119
|
+
|
|
114
120
|
dbc.prepare(sql, async function (err, stmt) {
|
|
115
121
|
if (err) {
|
|
116
122
|
err.query = sql
|
|
@@ -144,6 +150,9 @@ function _executeAsPreparedStatement(dbc, sql, values, reject, resolve) {
|
|
|
144
150
|
}
|
|
145
151
|
}
|
|
146
152
|
|
|
153
|
+
// REVISIT: better?
|
|
154
|
+
if (dbc._closed) return reject(new Error('Transaction is already closed'))
|
|
155
|
+
|
|
147
156
|
// on @sap/hana-client, we need to use execQuery in case of calling procedures
|
|
148
157
|
stmt[procedureName && dbc.name !== 'hdb' ? 'execQuery' : 'exec'](values, function (err, rows, ...args) {
|
|
149
158
|
if (err) {
|
|
@@ -188,6 +197,9 @@ function _executeSimpleSQL(dbc, sql, values) {
|
|
|
188
197
|
if (_hasValues(values) || !!_getProcedureName(sql)) {
|
|
189
198
|
_executeAsPreparedStatement(dbc, sql, values, reject, resolve)
|
|
190
199
|
} else {
|
|
200
|
+
// REVISIT: better?
|
|
201
|
+
if (dbc._closed) return reject(new Error('Transaction is already closed'))
|
|
202
|
+
|
|
191
203
|
dbc.exec(sql, function (err, result) {
|
|
192
204
|
if (err) {
|
|
193
205
|
err.query = sql
|
|
@@ -317,16 +329,26 @@ function executeInsertCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
|
317
329
|
})
|
|
318
330
|
}
|
|
319
331
|
|
|
332
|
+
function _writeStream(model, dbc, query, user, locale, txTimestamp, sql, values) {
|
|
333
|
+
if (dbc.name === 'hdb') return writeStreamWithHdb(dbc, sql, values)
|
|
334
|
+
|
|
335
|
+
// @sap/hana-client does not support WHERE EXISTS when writing stream
|
|
336
|
+
const subselect = query.UPDATE.where?.find(ele => typeof ele === 'object' && ele.SELECT && ele._etagValidation)
|
|
337
|
+
if (!subselect) return writeStreamWithHanaClient(dbc, sql, values)
|
|
338
|
+
|
|
339
|
+
const { sql: s, values: v } = _cqnToSQL(model, subselect, user, locale, txTimestamp)
|
|
340
|
+
return executePlainSQL(dbc, s, v).then(res => {
|
|
341
|
+
if (res.length === 0) return 0
|
|
342
|
+
return writeStreamWithHanaClient(dbc, sql, values)
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
|
|
320
346
|
function executeUpdateCQN(model, dbc, query, user, locale, txTimestamp) {
|
|
321
347
|
const { sql, values = [] } = _cqnToSQL(model, query, user, locale, txTimestamp)
|
|
322
348
|
|
|
323
349
|
// query can be insert from deep update
|
|
324
|
-
if (query.UPDATE && hasStreamUpdate(query.UPDATE, model))
|
|
325
|
-
|
|
326
|
-
return writeStreamWithHdb(dbc, sql, values)
|
|
327
|
-
}
|
|
328
|
-
return writeStreamWithHanaClient(dbc, sql, values)
|
|
329
|
-
}
|
|
350
|
+
if (query.UPDATE && hasStreamUpdate(query.UPDATE, model))
|
|
351
|
+
return _writeStream(model, dbc, query, user, locale, txTimestamp, sql, values)
|
|
330
352
|
|
|
331
353
|
return _executeSimpleSQL(dbc, sql, values)
|
|
332
354
|
}
|
|
@@ -1,111 +1,51 @@
|
|
|
1
|
-
// REVISIT: Remove @sap/instance-manager compat with CDS 7
|
|
2
|
-
|
|
3
1
|
const cds = require('../cds')
|
|
4
2
|
const LOG = cds.log('pool|db')
|
|
5
3
|
|
|
6
4
|
const { pool } = require('@sap/cds-foss')
|
|
7
5
|
const hana = require('./driver')
|
|
8
|
-
|
|
9
|
-
const _require = require('../common/utils/require')
|
|
10
6
|
const getError = require('../common/error')
|
|
11
7
|
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const oldIm =
|
|
22
|
-
cds.requires.multitenancy?.['old-instance-manager'] ??
|
|
23
|
-
cds.env.requires?.['cds.xt.DeploymentService']?.['old-instance-manager']
|
|
24
|
-
return oldIm ? null : cds.xt?.serviceManager
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function multiTenantInstanceManager(config = cds.env.requires.db) {
|
|
28
|
-
const { credentials } = config
|
|
29
|
-
|
|
30
|
-
if (!credentials) throw Object.assign(new Error('No database credentials provided'))
|
|
31
|
-
else if (typeof credentials !== 'object' || !(credentials.get_managed_instance_url || credentials.sm_url))
|
|
32
|
-
throw Object.assign(new Error('Malformed database credentials provided'))
|
|
33
|
-
|
|
34
|
-
// new instance manager
|
|
35
|
-
return new Promise((resolve, reject) => {
|
|
36
|
-
// REVISIT: better cache settings? current copied from old cds-hana...
|
|
37
|
-
// note: may need to be low for mtx tests -> configurable?
|
|
38
|
-
const opts = {
|
|
39
|
-
cache_max_items: 1,
|
|
40
|
-
cache_item_expire_seconds: 1
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// check binding to both managed-hana and service-manager for instance migration
|
|
44
|
-
if (cds.env.features.hybrid_instance_manager && process.env.VCAP_SERVICES) {
|
|
45
|
-
const vcap = JSON.parse(process.env.VCAP_SERVICES)
|
|
46
|
-
if (vcap['managed-hana'] && vcap['service-manager']) {
|
|
47
|
-
opts.smOpts = vcap['service-manager'][0].credentials
|
|
48
|
-
opts.imOpts = vcap['managed-hana'][0].credentials
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// no double config -> take passed credentials (= cds.env.requires.db.credentials)
|
|
53
|
-
if (!opts.smOpts) Object.assign(opts, credentials)
|
|
54
|
-
|
|
55
|
-
// REVISIT: should be relative
|
|
56
|
-
// const mtxPath = require.resolve('@sap/cds-mtx', { paths: [process.env.pwd(), __dirname] })
|
|
57
|
-
// const imPath = require.resolve('@sap/instance-manager', { paths: [mtxPath] })
|
|
58
|
-
// _require(imPath).create(opts, (err, res) => {
|
|
59
|
-
_require('@sap/instance-manager').create(opts, (err, res) => {
|
|
60
|
-
if (err) return reject(err)
|
|
61
|
-
resolve(res)
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function singleTenantInstanceManager(config = cds.env.requires.db) {
|
|
67
|
-
const { credentials } = config
|
|
68
|
-
|
|
69
|
-
if (!credentials) throw Object.assign(new Error('No database credentials provided'))
|
|
70
|
-
else if (typeof credentials !== 'object' || !credentials.host)
|
|
71
|
-
throw Object.assign(new Error('Malformed database credentials provided'))
|
|
72
|
-
|
|
73
|
-
// mock instance manager
|
|
74
|
-
return {
|
|
75
|
-
get: (_, cb) => cb(null, { credentials })
|
|
76
|
-
}
|
|
8
|
+
const _getMassagedCreds = function (creds) {
|
|
9
|
+
if (!('ca' in creds) && creds.certificate) creds.ca = creds.certificate
|
|
10
|
+
if ('encrypt' in creds && !('useTLS' in creds)) creds.useTLS = creds.encrypt
|
|
11
|
+
if ('hostname_in_certificate' in creds && !('sslHostNameInCertificate' in creds))
|
|
12
|
+
creds.sslHostNameInCertificate = creds.hostname_in_certificate
|
|
13
|
+
if ('validate_certificate' in creds && !('sslValidateCertificate' in creds))
|
|
14
|
+
creds.sslValidateCertificate = creds.validate_certificate
|
|
15
|
+
return creds
|
|
77
16
|
}
|
|
78
17
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
if (cds.xt?.serviceManager && !oldIm) {
|
|
91
|
-
return (await db._instance_manager.get(tenant, { disableCache: true })).credentials
|
|
18
|
+
// NOTE: disableCache: true means "force fetch credentials from service manager"
|
|
19
|
+
async function credentials4(tenant, { disableCache = false }) {
|
|
20
|
+
const { credentials } = cds.env.requires.db
|
|
21
|
+
if (!credentials) throw new Error('No database credentials provided')
|
|
22
|
+
if (cds.requires.multitenancy) {
|
|
23
|
+
// eslint-disable-next-line cds/no-missing-dependencies
|
|
24
|
+
const res = await require('@sap/cds-mtxs/lib').xt.serviceManager.get(tenant, { disableCache })
|
|
25
|
+
return _getMassagedCreds(res.credentials)
|
|
26
|
+
} else {
|
|
27
|
+
if (typeof credentials !== 'object' || !credentials.host) throw new Error('Malformed database credentials provided')
|
|
28
|
+
return _getMassagedCreds(credentials)
|
|
92
29
|
}
|
|
93
|
-
|
|
94
|
-
return new Promise((resolve, reject) => {
|
|
95
|
-
db._instance_manager.get(tenant, (err, res) => {
|
|
96
|
-
if (err) return reject(err)
|
|
97
|
-
if (!res) {
|
|
98
|
-
return reject(Object.assign(new Error(`There is no instance for tenant "${tenant}"`), { statusCode: 404 }))
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
resolve(res.credentials)
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
30
|
}
|
|
105
31
|
|
|
106
32
|
function factory4(creds, tenant) {
|
|
107
33
|
return {
|
|
108
|
-
create: () =>
|
|
34
|
+
create: () => {
|
|
35
|
+
return hana.__connect(creds, tenant).catch(e => {
|
|
36
|
+
if (!cds.requires.multitenancy || !e?.message.match(/authentication failed/i)) throw e
|
|
37
|
+
|
|
38
|
+
LOG._warn &&
|
|
39
|
+
LOG.warn(
|
|
40
|
+
`Possibly stale credentials for tenant ${tenant}, re-trying with fresh credentials from BTP Service Manager`
|
|
41
|
+
)
|
|
42
|
+
return credentials4(tenant, { disableCache: true }).then(_creds => {
|
|
43
|
+
// update creds in closure
|
|
44
|
+
creds = _creds
|
|
45
|
+
return hana.__connect(creds, tenant)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
},
|
|
109
49
|
destroy: client => hana.__disconnect(client),
|
|
110
50
|
validate: client => hana.__isConnected(client)
|
|
111
51
|
}
|
|
@@ -145,27 +85,6 @@ const _getPoolConfig = function () {
|
|
|
145
85
|
return mergedConfig
|
|
146
86
|
}
|
|
147
87
|
|
|
148
|
-
// REVISIT: copied from old cds-hana
|
|
149
|
-
const _getMassagedCreds = function (creds) {
|
|
150
|
-
if (!('ca' in creds) && creds.certificate) {
|
|
151
|
-
creds.ca = creds.certificate
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if ('encrypt' in creds && !('useTLS' in creds)) {
|
|
155
|
-
creds.useTLS = creds.encrypt
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
if ('hostname_in_certificate' in creds && !('sslHostNameInCertificate' in creds)) {
|
|
159
|
-
creds.sslHostNameInCertificate = creds.hostname_in_certificate
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if ('validate_certificate' in creds && !('sslValidateCertificate' in creds)) {
|
|
163
|
-
creds.sslValidateCertificate = creds.validate_certificate
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
return creds
|
|
167
|
-
}
|
|
168
|
-
|
|
169
88
|
const pools = new Map()
|
|
170
89
|
|
|
171
90
|
async function pool4(tenant, db) {
|
|
@@ -177,7 +96,7 @@ async function pool4(tenant, db) {
|
|
|
177
96
|
.then(creds => {
|
|
178
97
|
const config = _getPoolConfig()
|
|
179
98
|
LOG._info && LOG.info('effective pool configuration:', config)
|
|
180
|
-
const p = pool.createPool(factory4(
|
|
99
|
+
const p = pool.createPool(factory4(creds, tenant), config)
|
|
181
100
|
|
|
182
101
|
const INVALID_CREDENTIALS_WARNING = `Could not establish connection for tenant "${tenant}". Existing pool will be drained.`
|
|
183
102
|
const INVALID_CREDENTIALS_ERROR = new Error(
|
|
@@ -250,12 +169,7 @@ async function resilientAcquire(pool, attempts = 1) {
|
|
|
250
169
|
const message =
|
|
251
170
|
'Acquiring client from pool timed out. Please review your system setup, transaction handling, and pool configuration. ' +
|
|
252
171
|
`Pool State: borrowed: ${borrowed}, pending: ${pending}, size: ${size}, available: ${available}, max: ${max}`
|
|
253
|
-
err = getError(
|
|
254
|
-
Object.assign(err, {
|
|
255
|
-
statusCode: 503,
|
|
256
|
-
message
|
|
257
|
-
})
|
|
258
|
-
)
|
|
172
|
+
err = getError(Object.assign(err, { statusCode: 503, message }))
|
|
259
173
|
err._attempts = attempt
|
|
260
174
|
LOG._debug && LOG.debug(err)
|
|
261
175
|
throw err
|
|
@@ -28,43 +28,41 @@ const search2cqn4sql = (query, entity, options) => {
|
|
|
28
28
|
if (!cqnSearchPhrase) return query
|
|
29
29
|
const localizedAssociation = entity.associations?.localized
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
_addAliasToQuery(query, viewAlias)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const subQuery = cds.ql.SELECT.from(entity.name).columns(1)
|
|
40
|
-
subQuery.SELECT.from.as = targetAlias
|
|
41
|
-
const onCondition = _generateKeysWhereCondition(entity, targetAlias, textsAlias)
|
|
42
|
-
onCondition.push('and', { ref: [textsAlias, 'locale'] }, '=', { val: locale || "SESSION_CONTEXT('LOCALE')" })
|
|
43
|
-
|
|
44
|
-
// left outer join the target table with the _texts table (the _texts table contains the translated texts)
|
|
45
|
-
subQuery.leftJoin(localizedAssociation.target, textsAlias).on(onCondition)
|
|
46
|
-
|
|
47
|
-
// add condition for equal keys of target table and localized view
|
|
48
|
-
subQuery.where(_generateKeysWhereCondition(entity, targetAlias, viewAlias))
|
|
49
|
-
const containsColumns = _generateContainsColumns(columns2Search, entity)
|
|
50
|
-
|
|
51
|
-
let expression
|
|
52
|
-
|
|
53
|
-
if (isContainsPredicateSupported(query, entity, columns2Search)) {
|
|
54
|
-
// generate CQN expression with `CONTAINS` predicate for the columns from the target and text table
|
|
55
|
-
expression = search2Contains(cqnSearchPhrase, containsColumns)
|
|
56
|
-
Object.defineProperty(expression, 'searchUsingContains', { value: true, enumerable: true })
|
|
57
|
-
} else {
|
|
58
|
-
expression = searchToLike(cqnSearchPhrase, containsColumns)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
subQuery.where(expression)
|
|
62
|
-
|
|
63
|
-
// suppress the localize handler from redirecting the subQuery's target to the localized view
|
|
64
|
-
Object.defineProperty(subQuery, '_suppressLocalization', { value: true })
|
|
65
|
-
query.where('exists', subQuery)
|
|
66
|
-
return query
|
|
31
|
+
let { columns: columns2Search = computeColumnsToBeSearched(query, entity), locale } = options
|
|
32
|
+
const viewAlias = query.SELECT.from.as ? query.SELECT.from.as : 'LocalizedView'
|
|
33
|
+
|
|
34
|
+
if (!query.SELECT.from.as) {
|
|
35
|
+
_addAliasToQuery(query, viewAlias)
|
|
67
36
|
}
|
|
37
|
+
|
|
38
|
+
const subQuery = cds.ql.SELECT.from(entity.name).columns(1)
|
|
39
|
+
subQuery.SELECT.from.as = targetAlias
|
|
40
|
+
const onCondition = _generateKeysWhereCondition(entity, targetAlias, textsAlias)
|
|
41
|
+
onCondition.push('and', { ref: [textsAlias, 'locale'] }, '=', { val: locale || "SESSION_CONTEXT('LOCALE')" })
|
|
42
|
+
|
|
43
|
+
// left outer join the target table with the _texts table (the _texts table contains the translated texts)
|
|
44
|
+
subQuery.leftJoin(localizedAssociation.target, textsAlias).on(onCondition)
|
|
45
|
+
|
|
46
|
+
// add condition for equal keys of target table and localized view
|
|
47
|
+
subQuery.where(_generateKeysWhereCondition(entity, targetAlias, viewAlias))
|
|
48
|
+
const containsColumns = _generateContainsColumns(columns2Search, entity)
|
|
49
|
+
|
|
50
|
+
let expression
|
|
51
|
+
|
|
52
|
+
if (isContainsPredicateSupported(query, entity, columns2Search)) {
|
|
53
|
+
// generate CQN expression with `CONTAINS` predicate for the columns from the target and text table
|
|
54
|
+
expression = search2Contains(cqnSearchPhrase, containsColumns)
|
|
55
|
+
Object.defineProperty(expression, 'searchUsingContains', { value: true, enumerable: true })
|
|
56
|
+
} else {
|
|
57
|
+
expression = searchToLike(cqnSearchPhrase, containsColumns)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
subQuery.where(expression)
|
|
61
|
+
|
|
62
|
+
// suppress the localize handler from redirecting the subQuery's target to the localized view
|
|
63
|
+
Object.defineProperty(subQuery, '_suppressLocalization', { value: true })
|
|
64
|
+
query.where('exists', subQuery)
|
|
65
|
+
return query
|
|
68
66
|
}
|
|
69
67
|
|
|
70
68
|
const _generateKeysWhereCondition = (entity, alias1, alias2) => {
|
|
@@ -3,15 +3,11 @@ const _transform = o => ({ subdomain: o.subscribedSubdomain, tenant: o.subscribe
|
|
|
3
3
|
|
|
4
4
|
// REVISIT: Looks ugly -> can we simplify that?
|
|
5
5
|
const getTenantInfo = async tenant => {
|
|
6
|
-
const
|
|
7
|
-
const primaryKey = cds.mtx ? 'ID' : 'subscribedTenantId'
|
|
8
|
-
const path = cds.mtx ? 'tenant' : '/tenant' // HACK, otherwise the API doesn't work
|
|
9
|
-
|
|
10
|
-
const provisioning = await cds.connect.to(provisioningServiceName)
|
|
6
|
+
const provisioning = await cds.connect.to('cds.xt.SaasProvisioningService')
|
|
11
7
|
const tx = provisioning.tx({ user: new cds.User.Privileged() })
|
|
12
8
|
try {
|
|
13
9
|
const result = tenant
|
|
14
|
-
? _transform(await tx.get(
|
|
10
|
+
? _transform(await tx.get('/tenant', { ['subscribedTenantId']: tenant }))
|
|
15
11
|
: (await tx.read('tenant')).map(o => _transform(o))
|
|
16
12
|
await tx.commit()
|
|
17
13
|
return result
|
|
@@ -33,7 +33,8 @@ class EndpointRegistry {
|
|
|
33
33
|
// unsuccessful auth doesn't automatically reject!
|
|
34
34
|
paths.forEach(path => {
|
|
35
35
|
cds.app.use(path, (req, res, next) => {
|
|
36
|
-
|
|
36
|
+
// REVISIT: we should probably pass an error into next so that a (custom) error middleware can handle it
|
|
37
|
+
if (!req.user) res.status(401).json({ error: UNAUTHORIZED })
|
|
37
38
|
next()
|
|
38
39
|
})
|
|
39
40
|
})
|
|
@@ -116,6 +117,7 @@ class EndpointRegistry {
|
|
|
116
117
|
if (hasError) return res.status(500).send(results)
|
|
117
118
|
return res.status(201).send(results)
|
|
118
119
|
} catch (mtxError) {
|
|
120
|
+
// REVISIT: Still needed with cds-mtxs?
|
|
119
121
|
// If an unknown tenant id is provided, cds-mtx will crash ("Cannot read property 'hanaClient' of undefined")
|
|
120
122
|
return res.sendStatus(500)
|
|
121
123
|
}
|