@sap/cds 5.7.2 → 5.8.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 +108 -0
- package/app/fiori/routes.js +1 -1
- package/bin/deploy/to-hana/cfUtil.js +251 -138
- package/bin/deploy/to-hana/gitUtil.js +55 -0
- package/bin/deploy/to-hana/hana.js +92 -93
- package/bin/deploy/to-hana/hdiDeployUtil.js +42 -27
- package/bin/deploy/to-hana/index.js +14 -13
- package/bin/mtx/in-cds.js +1 -0
- package/bin/serve.js +1 -1
- package/bin/version.js +1 -0
- package/lib/compile/cdsc.js +0 -6
- package/lib/compile/minify.js +1 -1
- package/lib/compile/resolve.js +1 -1
- package/lib/compile/to/srvinfo.js +1 -1
- package/lib/core/classes.js +21 -1
- package/lib/env/index.js +3 -2
- package/lib/env/requires.js +4 -0
- package/lib/i18n/localize.js +5 -8
- package/lib/index.js +1 -0
- package/lib/log/errors.js +1 -1
- package/lib/ql/SELECT.js +2 -2
- package/lib/req/cds-context.js +1 -1
- package/lib/req/context.js +1 -1
- package/lib/serve/Transaction.js +9 -5
- package/lib/serve/index.js +13 -21
- package/lib/utils/tests.js +90 -66
- package/libx/_runtime/audit/generic/personal/modification.js +0 -8
- package/libx/_runtime/auth/index.js +7 -6
- package/libx/_runtime/auth/strategies/dwc.js +43 -0
- package/libx/_runtime/auth/utils.js +24 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +11 -38
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +12 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +7 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +24 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +43 -42
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +11 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/ExpressionToCQN.js +18 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/boundToCQN.js +1 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/deleteToCQN.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/expandToCQN.js +7 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/orderByToCQN.js +9 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +21 -30
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/utils.js +12 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/edm/AbstractEdmStructuredType.js +2 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriHelper.js +7 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/uri/UriTokenizer.js +5 -8
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +19 -47
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js +4 -11
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/ResourceJsonDeserializer.js +7 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/CommandExecutor.js +0 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/invocation/ConditionalRequestControlCommand.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/ContextURLFactory.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/serializer/TrustedResourceJsonSerializer.js +2 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/validator/ConditionalRequestValidator.js +6 -6
- package/libx/_runtime/cds-services/adapter/odata-v4/to.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +18 -5
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/handlerUtils.js +41 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/request.js +1 -17
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +80 -21
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/stream.js +7 -5
- package/libx/_runtime/cds-services/adapter/rest/Rest.js +22 -1
- package/libx/_runtime/cds-services/adapter/rest/handlers/read.js +8 -3
- package/libx/_runtime/cds-services/adapter/rest/utils/parse-url.js +3 -0
- package/libx/_runtime/cds-services/services/Service.js +1 -1
- package/libx/_runtime/cds-services/services/utils/columns.js +5 -1
- package/libx/_runtime/cds-services/services/utils/compareJson.js +15 -16
- package/libx/_runtime/cds-services/services/utils/differ.js +2 -8
- package/libx/_runtime/common/aspects/Association.js +16 -0
- package/libx/_runtime/common/composition/data.js +28 -37
- package/libx/_runtime/common/composition/delete.js +107 -58
- package/libx/_runtime/common/composition/index.js +2 -1
- package/libx/_runtime/common/composition/insert.js +13 -13
- package/libx/_runtime/common/composition/update.js +39 -34
- package/libx/_runtime/common/error/frontend.js +17 -2
- package/libx/_runtime/common/generic/auth.js +20 -85
- package/libx/_runtime/common/generic/crud.js +22 -1
- package/libx/_runtime/common/i18n/messages.properties +3 -0
- package/libx/_runtime/common/utils/cqn.js +2 -6
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +97 -123
- package/libx/_runtime/common/utils/csn.js +14 -3
- package/libx/_runtime/common/utils/foreignKeyPropagations.js +18 -1
- package/libx/_runtime/common/utils/keys.js +2 -1
- package/libx/_runtime/common/utils/path.js +1 -1
- package/libx/_runtime/common/utils/resolveView.js +12 -4
- package/libx/_runtime/common/utils/rewriteAsterisks.js +27 -13
- package/libx/_runtime/common/utils/search2cqn4sql.js +11 -6
- package/libx/_runtime/common/utils/structured.js +1 -1
- package/libx/_runtime/common/utils/vcap.js +27 -10
- package/libx/_runtime/db/data-conversion/post-processing.js +42 -35
- package/libx/_runtime/db/expand/expand-v2.js +21 -12
- package/libx/_runtime/db/expand/expandCQNToJoin.js +27 -29
- package/libx/_runtime/db/expand/index.js +3 -0
- package/libx/_runtime/db/generic/create.js +0 -10
- package/libx/_runtime/db/generic/index.js +3 -0
- package/libx/_runtime/db/generic/read.js +2 -24
- package/libx/_runtime/db/generic/rewrite.js +1 -3
- package/libx/_runtime/db/generic/update.js +1 -1
- package/libx/_runtime/db/query/delete.js +10 -4
- package/libx/_runtime/db/query/insert.js +3 -3
- package/libx/_runtime/db/query/read.js +15 -8
- package/libx/_runtime/db/query/update.js +5 -5
- package/libx/_runtime/db/sql-builder/ExpressionBuilder.js +9 -2
- package/libx/_runtime/db/sql-builder/FunctionBuilder.js +3 -0
- package/libx/_runtime/db/sql-builder/index.js +3 -0
- package/libx/_runtime/db/utils/columns.js +5 -2
- package/libx/_runtime/db/utils/deep.js +6 -8
- package/libx/_runtime/db/utils/generateAliases.js +56 -6
- package/libx/_runtime/fiori/generic/before.js +73 -49
- package/libx/_runtime/fiori/generic/edit.js +14 -18
- package/libx/_runtime/fiori/generic/patch.js +8 -11
- package/libx/_runtime/fiori/generic/read.js +22 -17
- package/libx/_runtime/fiori/generic/readOverDraft.js +1 -4
- package/libx/_runtime/hana/Service.js +1 -1
- package/libx/_runtime/hana/conversion.js +10 -0
- package/libx/_runtime/hana/execute.js +33 -16
- package/libx/_runtime/hana/localized.js +1 -1
- package/libx/_runtime/hana/search.js +3 -3
- package/libx/_runtime/hana/search2cqn4sql.js +22 -21
- package/libx/_runtime/hana/searchToContains.js +1 -1
- package/libx/_runtime/messaging/AMQPWebhookMessaging.js +4 -2
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +0 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/messaging/file-based.js +3 -1
- package/libx/_runtime/messaging/message-queuing-utils/options-messaging.js +1 -0
- package/libx/_runtime/messaging/service.js +16 -7
- package/libx/_runtime/remote/utils/client.js +33 -20
- package/libx/_runtime/remote/utils/data.js +53 -12
- package/libx/_runtime/sqlite/Service.js +1 -1
- package/libx/_runtime/sqlite/conversion.js +10 -0
- package/libx/_runtime/sqlite/localized.js +1 -1
- package/libx/_runtime/types/api.js +2 -2
- package/libx/gql/resolvers/parse/ast/enrich.js +1 -0
- package/libx/odata/afterburner.js +29 -6
- package/libx/odata/cqn2odata.js +9 -0
- package/libx/odata/grammar.pegjs +101 -45
- package/libx/odata/index.js +7 -1
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +2 -2
- package/libx/rest/RestAdapter.js +29 -1
- package/libx/rest/middleware/auth.js +1 -3
- package/libx/rest/middleware/parse.js +1 -0
- package/package.json +1 -1
- package/server.js +1 -1
- package/bin/deploy/to-hana/logger.js +0 -27
- package/bin/deploy/to-hana/runCommand.js +0 -113
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/selectHelper.js +0 -37
- package/libx/_runtime/common/utils/auth.js +0 -16
|
@@ -5,13 +5,15 @@ const {
|
|
|
5
5
|
Components: { DATA_CREATE_HANDLER }
|
|
6
6
|
} = require('../okra/odata-server')
|
|
7
7
|
|
|
8
|
-
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
9
8
|
const { validateResourcePath } = require('../utils/request')
|
|
10
9
|
const { isReturnMinimal } = require('../utils/handlerUtils')
|
|
11
10
|
const readAfterWrite = require('../utils/readAfterWrite')
|
|
12
11
|
const { toODataResult, postProcess, postProcessMinimal } = require('../utils/result')
|
|
12
|
+
|
|
13
13
|
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
14
14
|
|
|
15
|
+
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
16
|
+
|
|
15
17
|
/**
|
|
16
18
|
* The handler that will be registered with odata-v4.
|
|
17
19
|
*
|
|
@@ -21,6 +23,7 @@ const { mergeJson } = require('../../../services/utils/compareJson')
|
|
|
21
23
|
const create = service => {
|
|
22
24
|
return async (odataReq, odataRes, next) => {
|
|
23
25
|
let req
|
|
26
|
+
|
|
24
27
|
try {
|
|
25
28
|
validateResourcePath(odataReq, service)
|
|
26
29
|
req = new ODataRequest(DATA_CREATE_HANDLER, service, odataReq, odataRes)
|
|
@@ -33,9 +36,12 @@ const create = service => {
|
|
|
33
36
|
cds.context = tx
|
|
34
37
|
|
|
35
38
|
let result, err
|
|
39
|
+
|
|
36
40
|
try {
|
|
37
41
|
result = await tx.dispatch(req)
|
|
38
42
|
|
|
43
|
+
// REVISIT readAfterWrite -> commit -> postProcessing
|
|
44
|
+
|
|
39
45
|
if (isReturnMinimal(req)) {
|
|
40
46
|
postProcessMinimal(req, result)
|
|
41
47
|
} else {
|
|
@@ -60,12 +66,13 @@ const create = service => {
|
|
|
60
66
|
}
|
|
61
67
|
} catch (e) {
|
|
62
68
|
err = e
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
await tx.rollback(e).catch(() => {})
|
|
66
|
-
} else if (changeset) {
|
|
69
|
+
|
|
70
|
+
if (changeset) {
|
|
67
71
|
// for passing into rollback
|
|
68
72
|
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
73
|
+
} else {
|
|
74
|
+
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
75
|
+
await tx.rollback(e).catch(() => {})
|
|
69
76
|
}
|
|
70
77
|
} finally {
|
|
71
78
|
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req._.req))
|
|
@@ -17,6 +17,7 @@ const { validateResourcePath } = require('../utils/request')
|
|
|
17
17
|
const del = service => {
|
|
18
18
|
return async (odataReq, odataRes, next) => {
|
|
19
19
|
let req
|
|
20
|
+
|
|
20
21
|
try {
|
|
21
22
|
validateResourcePath(odataReq, service)
|
|
22
23
|
req = new ODataRequest(DATA_DELETE_HANDLER, service, odataReq, odataRes)
|
|
@@ -29,6 +30,7 @@ const del = service => {
|
|
|
29
30
|
cds.context = tx
|
|
30
31
|
|
|
31
32
|
let err
|
|
33
|
+
|
|
32
34
|
try {
|
|
33
35
|
await tx.dispatch(req)
|
|
34
36
|
const result = null
|
|
@@ -41,12 +43,13 @@ const del = service => {
|
|
|
41
43
|
}
|
|
42
44
|
} catch (e) {
|
|
43
45
|
err = e
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
await tx.rollback(e).catch(() => {})
|
|
47
|
-
} else if (changeset) {
|
|
46
|
+
|
|
47
|
+
if (changeset) {
|
|
48
48
|
// for passing into rollback
|
|
49
49
|
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
50
|
+
} else {
|
|
51
|
+
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
52
|
+
await tx.rollback(e).catch(() => {})
|
|
50
53
|
}
|
|
51
54
|
} finally {
|
|
52
55
|
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req._.req))
|
|
@@ -49,6 +49,19 @@ const _betterOkraError = err => {
|
|
|
49
49
|
if (err.name === 'DeserializationError') {
|
|
50
50
|
err.message = err.message.replace('Error while deserializing payload.', 'Deserialization Error:')
|
|
51
51
|
err.message = err.message.replace(' An error occurred during deserialization of the entity.', '')
|
|
52
|
+
|
|
53
|
+
// add parsed data to error for req.data
|
|
54
|
+
let e = err
|
|
55
|
+
let depth = 0
|
|
56
|
+
while (e._rootCause && depth < 10) {
|
|
57
|
+
const rootCause = e._rootCause
|
|
58
|
+
if (rootCause._data) {
|
|
59
|
+
err._data = rootCause._data
|
|
60
|
+
break
|
|
61
|
+
}
|
|
62
|
+
e = rootCause
|
|
63
|
+
depth++
|
|
64
|
+
}
|
|
52
65
|
} else if (err.name === 'SerializationError') {
|
|
53
66
|
err.message = err.message.replace('An error occurred during serialization of the entity.', 'Serialization Error:')
|
|
54
67
|
err.message = err.message.replace(
|
|
@@ -68,10 +81,16 @@ const _betterOkraError = err => {
|
|
|
68
81
|
* @returns {Function}
|
|
69
82
|
*/
|
|
70
83
|
const getErrorHandler = (crashOnError = true, srv) => {
|
|
71
|
-
return (odataReq, odataRes, next, err) => {
|
|
84
|
+
return async (odataReq, odataRes, next, err) => {
|
|
72
85
|
// REVISIT: crashOnError
|
|
73
86
|
if (isStandardError(err) && crashOnError) {
|
|
74
|
-
|
|
87
|
+
// first rollback in case of atomicity groups
|
|
88
|
+
const changeset = odataReq.getAtomicityGroupId()
|
|
89
|
+
if (changeset) {
|
|
90
|
+
const tx = odataReq.getBatchApplicationData().txs[changeset]
|
|
91
|
+
await tx.rollback().catch(() => {})
|
|
92
|
+
}
|
|
93
|
+
|
|
75
94
|
throw err
|
|
76
95
|
}
|
|
77
96
|
|
|
@@ -84,9 +103,11 @@ const getErrorHandler = (crashOnError = true, srv) => {
|
|
|
84
103
|
req = odataReq.getIncomingRequest()
|
|
85
104
|
}
|
|
86
105
|
|
|
87
|
-
if (
|
|
106
|
+
if (typeof err.getRootCause === 'function') {
|
|
88
107
|
// > an OKRA error
|
|
89
108
|
err = _betterOkraError(err)
|
|
109
|
+
// add req.data for use in custom error handler in case of okra deserialization error
|
|
110
|
+
if ('_data' in err && !('data' in req)) req.data = err._data
|
|
90
111
|
}
|
|
91
112
|
|
|
92
113
|
// invoke srv.on('error', function (err, req) { ... }) here in special situations
|
|
@@ -14,12 +14,13 @@ const {
|
|
|
14
14
|
}
|
|
15
15
|
} = require('../okra/odata-server')
|
|
16
16
|
|
|
17
|
-
const { isCustomOperation
|
|
17
|
+
const { isCustomOperation } = require('../utils/request')
|
|
18
18
|
const { actionAndFunctionQueries, getActionOrFunctionReturnType } = require('../utils/handlerUtils')
|
|
19
19
|
const { validateResourcePath } = require('../utils/request')
|
|
20
20
|
const { toODataResult, postProcess } = require('../utils/result')
|
|
21
21
|
const { isStreaming, getStreamProperties } = require('../utils/stream')
|
|
22
22
|
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
23
|
+
|
|
23
24
|
const getError = require('../../../../common/error')
|
|
24
25
|
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
25
26
|
|
|
@@ -46,7 +47,7 @@ const _selectOrExpandInQueryOptions = queryOptions => {
|
|
|
46
47
|
* @private
|
|
47
48
|
*/
|
|
48
49
|
const _invokeFunction = async (tx, req, odataReq) => {
|
|
49
|
-
|
|
50
|
+
let result = await tx.dispatch(req)
|
|
50
51
|
|
|
51
52
|
const functionReturnType = getActionOrFunctionReturnType(
|
|
52
53
|
odataReq.getUriInfo().getPathSegments(),
|
|
@@ -59,7 +60,7 @@ const _invokeFunction = async (tx, req, odataReq) => {
|
|
|
59
60
|
functionReturnType.kind === 'entity' &&
|
|
60
61
|
_selectOrExpandInQueryOptions(odataReq.getQueryOptions())
|
|
61
62
|
) {
|
|
62
|
-
await actionAndFunctionQueries(req, odataReq, result, tx, functionReturnType)
|
|
63
|
+
result = await actionAndFunctionQueries(req, odataReq, result, tx, functionReturnType)
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
return toODataResult(result, req)
|
|
@@ -142,9 +143,9 @@ const _isCollection = segments => {
|
|
|
142
143
|
* @returns {boolean}
|
|
143
144
|
* @private
|
|
144
145
|
*/
|
|
145
|
-
const _isNavigationToOne = segments =>
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
const _isNavigationToOne = segments =>
|
|
147
|
+
segments[segments.length - 1].getKind() === NAVIGATION_TO_ONE &&
|
|
148
|
+
segments[segments.length - 1].getKeyPredicates().length === 0
|
|
148
149
|
|
|
149
150
|
const _hasRedirectProperty = elements => {
|
|
150
151
|
return Object.values(elements).some(val => {
|
|
@@ -229,22 +230,23 @@ const _readEntityOrProperty = async (tx, req, segments) => {
|
|
|
229
230
|
|
|
230
231
|
if (propertyElement === null) {
|
|
231
232
|
_transformRedirectProperties(req, result)
|
|
233
|
+
|
|
232
234
|
return toODataResult(result[0])
|
|
233
235
|
}
|
|
234
236
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
+
result = _getResult(resolveStructuredName(segments, segments.length - 2), result[0])
|
|
238
|
+
|
|
239
|
+
if (req.target._etag) result.$etag = result[req.target._etag]
|
|
237
240
|
|
|
238
|
-
const odataResult = toODataResult(
|
|
239
|
-
if (req.target._etag) odataResult['*@odata.etag'] = res[req.target._etag]
|
|
241
|
+
const odataResult = toODataResult(result, req)
|
|
240
242
|
|
|
241
243
|
// property is read via a to one association and last segment is not $value
|
|
242
244
|
if (index !== 2 && segments.length > 2 && segments[segments.length - 2].getKind() === NAVIGATION_TO_ONE) {
|
|
243
245
|
// find keys in result
|
|
244
|
-
const keys = Object.keys(result
|
|
246
|
+
const keys = Object.keys(result)
|
|
245
247
|
.filter(k => segments[segments.length - index - 1].getEdmType().getOwnKeyPropertyRefs().has(k))
|
|
246
248
|
.reduce((res, curr) => {
|
|
247
|
-
res[curr] = result[
|
|
249
|
+
res[curr] = result[curr]
|
|
248
250
|
return res
|
|
249
251
|
}, {})
|
|
250
252
|
|
|
@@ -266,20 +268,18 @@ const _readEntityOrProperty = async (tx, req, segments) => {
|
|
|
266
268
|
*/
|
|
267
269
|
const _readCollection = async (tx, req, odataReq) => {
|
|
268
270
|
const result = (await tx.dispatch(req)) || []
|
|
269
|
-
const odataResult = toODataResult(result, req)
|
|
270
271
|
|
|
271
|
-
|
|
272
|
-
if (!odataResult['*@odata.count'] && req.query.SELECT.count) {
|
|
273
|
-
odataResult['*@odata.count'] = 0
|
|
274
|
-
}
|
|
272
|
+
if (req.query.SELECT.count && !('$count' in result)) result.$count = 0
|
|
275
273
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
274
|
+
const limit = req.query.SELECT.limit && req.query.SELECT.limit.rows && req.query.SELECT.limit.rows.val
|
|
275
|
+
const top = odataReq.getUriInfo().getQueryOption(QueryOptions.TOP)
|
|
276
|
+
if (limit && limit === result.length && limit !== top && !('$nextLink' in result)) {
|
|
277
|
+
const token = odataReq.getUriInfo().getQueryOption(QueryOptions.SKIPTOKEN)
|
|
278
|
+
result.$nextLink = (token ? parseInt(token) : 0) + limit
|
|
281
279
|
}
|
|
282
280
|
|
|
281
|
+
const odataResult = toODataResult(result, req)
|
|
282
|
+
|
|
283
283
|
_transformRedirectProperties(req, result)
|
|
284
284
|
|
|
285
285
|
return odataResult
|
|
@@ -298,7 +298,7 @@ const _readCollection = async (tx, req, odataReq) => {
|
|
|
298
298
|
const _readStream = async (tx, req) => {
|
|
299
299
|
req.query._streaming = true
|
|
300
300
|
|
|
301
|
-
const { contentType,
|
|
301
|
+
const { contentType, contentDispositionFilename, contentDispositionType } = await getStreamProperties(req, tx.model)
|
|
302
302
|
|
|
303
303
|
let result = await tx.dispatch(req)
|
|
304
304
|
|
|
@@ -312,14 +312,14 @@ const _readStream = async (tx, req) => {
|
|
|
312
312
|
|
|
313
313
|
if (result[0] === null) return null
|
|
314
314
|
|
|
315
|
-
|
|
316
|
-
const
|
|
315
|
+
result = result[0]
|
|
316
|
+
const { value: readable } = result
|
|
317
317
|
|
|
318
|
-
if (
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
//
|
|
322
|
-
|
|
318
|
+
if (readable) {
|
|
319
|
+
readable.on('error', () => {
|
|
320
|
+
readable.removeAllListeners('error')
|
|
321
|
+
// readable.destroy() does not end stream in node 10 and 12
|
|
322
|
+
readable.push(null)
|
|
323
323
|
})
|
|
324
324
|
}
|
|
325
325
|
|
|
@@ -336,11 +336,13 @@ const _readStream = async (tx, req) => {
|
|
|
336
336
|
req.reject(406, `Content type "${contentType}" not listed in accept header "${headers.accept}".`)
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
-
if (contentType)
|
|
340
|
-
if (
|
|
341
|
-
|
|
339
|
+
if (contentType) result.$mediaContentType = contentType
|
|
340
|
+
if (contentDispositionFilename) {
|
|
341
|
+
result.$mediaContentDispositionFilename = contentDispositionFilename
|
|
342
|
+
if (contentDispositionType) result.$mediaContentDispositionType = contentDispositionType
|
|
343
|
+
}
|
|
342
344
|
|
|
343
|
-
return
|
|
345
|
+
return toODataResult(result, req)
|
|
344
346
|
}
|
|
345
347
|
|
|
346
348
|
const _readSingleton = async (tx, req, lastSegment) => {
|
|
@@ -377,10 +379,6 @@ const _readAndTransform = (tx, req, odataReq) => {
|
|
|
377
379
|
}
|
|
378
380
|
|
|
379
381
|
if (_isCollection(segments)) {
|
|
380
|
-
if (odataReq.getUriInfo().getQueryOption(QueryOptions.COUNT)) {
|
|
381
|
-
req.query.SELECT.count = true
|
|
382
|
-
}
|
|
383
|
-
|
|
384
382
|
return _readCollection(tx, req, odataReq)
|
|
385
383
|
}
|
|
386
384
|
|
|
@@ -443,6 +441,7 @@ const _removeKeysForParams = result => {
|
|
|
443
441
|
const read = service => {
|
|
444
442
|
return async (odataReq, odataRes, next) => {
|
|
445
443
|
let req
|
|
444
|
+
|
|
446
445
|
try {
|
|
447
446
|
validateResourcePath(odataReq, service)
|
|
448
447
|
req = new ODataRequest(DATA_READ_HANDLER, service, odataReq, odataRes)
|
|
@@ -456,6 +455,7 @@ const read = service => {
|
|
|
456
455
|
|
|
457
456
|
let result, err
|
|
458
457
|
let additional = {}
|
|
458
|
+
|
|
459
459
|
try {
|
|
460
460
|
// REVISIT: refactor _readAndTransform
|
|
461
461
|
result = await _readAndTransform(tx, req, odataReq)
|
|
@@ -475,12 +475,13 @@ const read = service => {
|
|
|
475
475
|
}
|
|
476
476
|
} catch (e) {
|
|
477
477
|
err = e
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
await tx.rollback(e).catch(() => {})
|
|
481
|
-
} else if (changeset) {
|
|
478
|
+
|
|
479
|
+
if (changeset) {
|
|
482
480
|
// for passing into rollback
|
|
483
481
|
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
482
|
+
} else {
|
|
483
|
+
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
484
|
+
await tx.rollback(e).catch(() => {})
|
|
484
485
|
}
|
|
485
486
|
} finally {
|
|
486
487
|
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req._.req))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
2
|
|
|
3
|
-
const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../../
|
|
3
|
+
const { UNAUTHORIZED, FORBIDDEN, getRequiresAsArray } = require('../../../../auth/utils')
|
|
4
4
|
|
|
5
5
|
module.exports = srv => {
|
|
6
6
|
const requires = getRequiresAsArray(srv.definition)
|
|
@@ -6,14 +6,16 @@ const {
|
|
|
6
6
|
Components: { DATA_UPDATE_HANDLER, DATA_CREATE_HANDLER }
|
|
7
7
|
} = require('../okra/odata-server')
|
|
8
8
|
|
|
9
|
-
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
10
9
|
const { validateResourcePath } = require('../utils/request')
|
|
11
10
|
const { isReturnMinimal } = require('../utils/handlerUtils')
|
|
12
11
|
const readAfterWrite = require('../utils/readAfterWrite')
|
|
13
12
|
const { toODataResult, postProcess, postProcessMinimal } = require('../utils/result')
|
|
14
13
|
const { hasOmitValuesPreference } = require('../utils/omitValues')
|
|
14
|
+
|
|
15
15
|
const { mergeJson } = require('../../../services/utils/compareJson')
|
|
16
16
|
|
|
17
|
+
const { getSapMessages } = require('../../../../common/error/frontend')
|
|
18
|
+
|
|
17
19
|
const _isUpsertAllowed = target => {
|
|
18
20
|
return !(cds.env.runtime && cds.env.runtime.allow_upsert === false) && !(target && target._isDraftEnabled)
|
|
19
21
|
}
|
|
@@ -126,6 +128,7 @@ const _shouldReadPreviousResult = req =>
|
|
|
126
128
|
const update = service => {
|
|
127
129
|
return async (odataReq, odataRes, next) => {
|
|
128
130
|
let req
|
|
131
|
+
|
|
129
132
|
try {
|
|
130
133
|
validateResourcePath(odataReq, service)
|
|
131
134
|
req = new ODataRequest(DATA_UPDATE_HANDLER, service, odataReq, odataRes)
|
|
@@ -141,8 +144,10 @@ const update = service => {
|
|
|
141
144
|
const primitive = odataReq.getUriInfo().getLastSegment().getKind() === 'PRIMITIVE.PROPERTY'
|
|
142
145
|
|
|
143
146
|
let result, err
|
|
147
|
+
|
|
144
148
|
try {
|
|
145
149
|
let previousResult
|
|
150
|
+
|
|
146
151
|
if (_shouldReadPreviousResult(req)) {
|
|
147
152
|
previousResult = await _readAfterWriteAndVirtuals(req, service, result)
|
|
148
153
|
}
|
|
@@ -173,12 +178,13 @@ const update = service => {
|
|
|
173
178
|
}
|
|
174
179
|
} catch (e) {
|
|
175
180
|
err = e
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
await tx.rollback(e).catch(() => {})
|
|
179
|
-
} else if (changeset) {
|
|
181
|
+
|
|
182
|
+
if (changeset) {
|
|
180
183
|
// for passing into rollback
|
|
181
184
|
odataReq.getBatchApplicationData().errors[changeset].push({ error: e, req })
|
|
185
|
+
} else {
|
|
186
|
+
// REVISIT: rollback needed if an error occurred before commit attempted -> how to distinguish?
|
|
187
|
+
await tx.rollback(e).catch(() => {})
|
|
182
188
|
}
|
|
183
189
|
} finally {
|
|
184
190
|
req.messages && odataRes.setHeader('sap-messages', getSapMessages(req.messages, req._.req))
|
|
@@ -58,17 +58,26 @@ class ExpressionToCQN {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
-
_lambda(pathSegments) {
|
|
61
|
+
_lambda(pathSegments, operator) {
|
|
62
62
|
// we don't care about the variable name
|
|
63
63
|
if (pathSegments[0].getKind() === 'EXPRESSION.VARIABLE') pathSegments = pathSegments.slice(1)
|
|
64
64
|
const nav =
|
|
65
65
|
pathSegments.length > 2 ? pathSegments.slice(0, pathSegments.length - 2).map(this._segmentFromMember) : []
|
|
66
66
|
const navName = this._segmentFromMember(pathSegments[pathSegments.length - 2])
|
|
67
|
-
|
|
67
|
+
let condition = this._segmentFromMember(pathSegments[pathSegments.length - 1])
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
69
|
+
// in case of functions, condition is an object
|
|
70
|
+
if (condition && !Array.isArray(condition)) condition = [condition]
|
|
71
|
+
|
|
72
|
+
if (pathSegments[pathSegments.length - 1].getKind() === 'ALL.EXPRESSION') {
|
|
73
|
+
return [
|
|
74
|
+
...(operator === 'not' ? [] : ['not']),
|
|
75
|
+
'exists',
|
|
76
|
+
{ ref: [...nav, { id: navName, where: ['not', { xpr: condition }] }] }
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return [...(operator === 'not' ? ['not'] : []), 'exists', { ref: [...nav, { id: navName, where: condition }] }]
|
|
72
81
|
}
|
|
73
82
|
|
|
74
83
|
_segmentFromMember(segment) {
|
|
@@ -100,6 +109,7 @@ class ExpressionToCQN {
|
|
|
100
109
|
const name = this._segmentFromMember(segment)
|
|
101
110
|
const where =
|
|
102
111
|
nextSegments &&
|
|
112
|
+
nextSegments.length &&
|
|
103
113
|
nextSegments[nextSegments.length - 1].getKind() === ResourceKind.COUNT &&
|
|
104
114
|
segment.getKeyPredicates().reduce((prev, curr) => {
|
|
105
115
|
if (prev.length > 0) prev.push('and')
|
|
@@ -133,14 +143,14 @@ class ExpressionToCQN {
|
|
|
133
143
|
return [this._segmentFromMember(segment)]
|
|
134
144
|
}
|
|
135
145
|
|
|
136
|
-
_member(expression) {
|
|
146
|
+
_member(expression, operator) {
|
|
137
147
|
const pathSegments = expression.getPathSegments()
|
|
138
148
|
if (
|
|
139
149
|
pathSegments.some(segment =>
|
|
140
150
|
[ResourceKind.ANY_EXPRESSION, ResourceKind.ALL_EXPRESSION].includes(segment.getKind())
|
|
141
151
|
)
|
|
142
152
|
) {
|
|
143
|
-
return this._lambda(pathSegments)
|
|
153
|
+
return this._lambda(pathSegments, operator)
|
|
144
154
|
}
|
|
145
155
|
|
|
146
156
|
const members = this._getMemberRecursively(pathSegments)
|
|
@@ -305,7 +315,7 @@ class ExpressionToCQN {
|
|
|
305
315
|
return this._convert(expression)
|
|
306
316
|
|
|
307
317
|
case ExpressionKind.MEMBER:
|
|
308
|
-
return this._member(expression)
|
|
318
|
+
return this._member(expression, operator)
|
|
309
319
|
|
|
310
320
|
case ExpressionKind.METHOD:
|
|
311
321
|
return this._method(expression, operator)
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
|
-
const { isPathSupported } = require('./
|
|
5
|
-
const { convertUrlPathToCqn } = require('./utils')
|
|
4
|
+
const { convertUrlPathToCqn, isPathSupported } = require('./utils')
|
|
6
5
|
|
|
7
6
|
const {
|
|
8
7
|
BOUND_ACTION,
|
|
@@ -26,7 +26,6 @@ const deleteToCQN = (service, odataReq) => {
|
|
|
26
26
|
if (SUPPORTED_KINDS[segment.getKind()]) {
|
|
27
27
|
return DELETE.from(convertUrlPathToCqn(segments, service))
|
|
28
28
|
}
|
|
29
|
-
|
|
30
29
|
if (segment.getKind() === 'PRIMITIVE.PROPERTY') {
|
|
31
30
|
return UPDATE(convertUrlPathToCqn(segments, service)).data({ [segment.getProperty().getName()]: null })
|
|
32
31
|
}
|
|
@@ -12,6 +12,7 @@ const ExpressionToCQN = require('./ExpressionToCQN')
|
|
|
12
12
|
const { getColumns } = require('../../../services/utils/columns')
|
|
13
13
|
const { addLimit, isSameArray } = require('./utils')
|
|
14
14
|
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
15
|
+
const getError = require('../../../../common/error')
|
|
15
16
|
/**
|
|
16
17
|
* Check which element(s) of the entity has been expanded.
|
|
17
18
|
*
|
|
@@ -28,7 +29,11 @@ const _getExpandItem = (isAll, expandItems, name) => {
|
|
|
28
29
|
|
|
29
30
|
return expandItems.find(item => {
|
|
30
31
|
const pathSegments = item.getPathSegments()
|
|
31
|
-
|
|
32
|
+
if (pathSegments[pathSegments.length - 1].getKind() === 'COUNT') {
|
|
33
|
+
throw getError(501, 'EXPAND_COUNT_UNSUPPORTED')
|
|
34
|
+
}
|
|
35
|
+
const navigation = pathSegments[pathSegments.length - 1].getNavigationProperty()
|
|
36
|
+
return navigation && navigation.getName() === name
|
|
32
37
|
})
|
|
33
38
|
}
|
|
34
39
|
|
|
@@ -228,7 +233,7 @@ const expandToCQN = (model, expandItems, type, options) => {
|
|
|
228
233
|
const allElements = []
|
|
229
234
|
const isAll = expandItems.some(item => item.isAll())
|
|
230
235
|
|
|
231
|
-
for (const [name, navigationProperty] of type.getNavigationProperties()) {
|
|
236
|
+
for (const [name, navigationProperty] of type.getNavigationProperties(isAll)) {
|
|
232
237
|
const expandItem = _getExpandItem(isAll, expandItems, name)
|
|
233
238
|
|
|
234
239
|
if (isAll || expandItem) {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
const ExpressionToCQN = require('./ExpressionToCQN')
|
|
2
2
|
const { getFeatureNotSupportedError } = require('../../../util/errors')
|
|
3
3
|
const odata = require('../okra/odata-server')
|
|
4
|
+
const { ResourceKind } = require('../okra/odata-commons/uri/UriResource')
|
|
4
5
|
const {
|
|
5
6
|
QueryOptions: { ORDERBY }
|
|
6
7
|
} = odata
|
|
7
8
|
const ExpressionKind = odata.uri.Expression.ExpressionKind
|
|
9
|
+
const getError = require('../../../../common/error')
|
|
8
10
|
|
|
9
11
|
const _buildNavRef = pathSegment => {
|
|
10
12
|
return pathSegment.getProperty() ? pathSegment.getProperty().getName() : pathSegment.getNavigationProperty().getName()
|
|
@@ -13,6 +15,13 @@ const _buildNavRef = pathSegment => {
|
|
|
13
15
|
const _orderExpression = order => {
|
|
14
16
|
if (order.getExpression().getKind() === ExpressionKind.MEMBER) {
|
|
15
17
|
const ref = []
|
|
18
|
+
const lastSegment = order.getExpression().getPathSegments()[order.getExpression().getPathSegments().length - 1]
|
|
19
|
+
if (
|
|
20
|
+
lastSegment.getKind() === ResourceKind.ANY_EXPRESSION ||
|
|
21
|
+
lastSegment.getKind() === ResourceKind.ALL_EXPRESSION
|
|
22
|
+
) {
|
|
23
|
+
throw getError(501, 'ORDERBY_LAMBDA_UNSUPPORTED')
|
|
24
|
+
}
|
|
16
25
|
for (let i = 0; i < order.getExpression().getPathSegments().length; i++) {
|
|
17
26
|
ref.push(_buildNavRef(order.getExpression().getPathSegments()[i]))
|
|
18
27
|
}
|
|
@@ -1,22 +1,14 @@
|
|
|
1
1
|
const cds = require('../../../../cds')
|
|
2
2
|
const { SELECT } = cds.ql
|
|
3
3
|
|
|
4
|
-
const QueryOptions = require('../okra/odata-server').QueryOptions
|
|
5
|
-
const { isNavigation, isPathSupported } = require('./selectHelper')
|
|
6
|
-
const { isViewWithParams, getValidationQuery } = require('./selectHelper')
|
|
7
|
-
const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
|
|
8
4
|
const ExpressionToCQN = require('./ExpressionToCQN')
|
|
9
5
|
const orderByToCQN = require('./orderByToCQN')
|
|
10
6
|
const selectToCQN = require('./selectToCQN')
|
|
11
7
|
const searchToCQN = require('./searchToCQN')
|
|
12
8
|
const applyToCQN = require('./applyToCQN')
|
|
13
|
-
const {
|
|
14
|
-
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
15
|
-
const { isStreaming } = require('../utils/stream')
|
|
16
|
-
const { convertUrlPathToCqn, getAllKeys } = require('./utils')
|
|
17
|
-
const { getMaxPageSize, getDefaultPageSize } = require('../../../../common/utils/page')
|
|
18
|
-
const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
|
|
9
|
+
const { convertUrlPathToCqn, getAllKeys, isPathSupported } = require('./utils')
|
|
19
10
|
|
|
11
|
+
const QueryOptions = require('../okra/odata-server').QueryOptions
|
|
20
12
|
const {
|
|
21
13
|
COUNT,
|
|
22
14
|
ENTITY,
|
|
@@ -28,7 +20,6 @@ const {
|
|
|
28
20
|
VALUE,
|
|
29
21
|
SINGLETON
|
|
30
22
|
} = require('../okra/odata-server').uri.UriResource.ResourceKind
|
|
31
|
-
|
|
32
23
|
const SUPPORTED_SEGMENT_KINDS = {
|
|
33
24
|
[ENTITY]: 1,
|
|
34
25
|
[ENTITY_COLLECTION]: 1,
|
|
@@ -41,9 +32,16 @@ const SUPPORTED_SEGMENT_KINDS = {
|
|
|
41
32
|
[SINGLETON]: 1
|
|
42
33
|
}
|
|
43
34
|
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
}
|
|
35
|
+
const { _expand } = require('../utils/handlerUtils')
|
|
36
|
+
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
37
|
+
const { isStreaming } = require('../utils/stream')
|
|
38
|
+
|
|
39
|
+
const { getMaxPageSize, getDefaultPageSize } = require('../../../../common/utils/page')
|
|
40
|
+
const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
|
|
41
|
+
|
|
42
|
+
const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
|
|
43
|
+
|
|
44
|
+
const _applyOnlyContainsFilter = apply => Object.keys(apply).length === 1 && apply.filter
|
|
47
45
|
|
|
48
46
|
/**
|
|
49
47
|
*
|
|
@@ -184,6 +182,7 @@ const _extendCqnWithApply = (cqn, apply, entity) => {
|
|
|
184
182
|
|
|
185
183
|
// REVISIT only execute on HANA?
|
|
186
184
|
cqn.SELECT.columns = _groupByPathExpressionsToExpand(cqn, entity)
|
|
185
|
+
if (cqn.SELECT.count) cqn.SELECT.__countAggregated = true
|
|
187
186
|
}
|
|
188
187
|
|
|
189
188
|
const _containsSelectedColumn = (o, selectColumns) => {
|
|
@@ -231,14 +230,6 @@ const _checkViewWithParamCall = (isView, segments, kind, name) => {
|
|
|
231
230
|
}
|
|
232
231
|
}
|
|
233
232
|
|
|
234
|
-
const addValidationQueryIfRequired = (segments, isView, cqn, service, kind) => {
|
|
235
|
-
if (isNavigation(segments) && !isView && (kind === NAVIGATION_TO_MANY || kind === NAVIGATION_TO_ONE)) {
|
|
236
|
-
cqn._validationQuery = getValidationQuery(cqn.SELECT.from.ref, service.model)
|
|
237
|
-
cqn._validationQuery.__navToManyWithKeys =
|
|
238
|
-
kind === NAVIGATION_TO_ONE && segments[segments.length - 1].getKeyPredicates().length !== 0
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
233
|
const _addKeysToSelectIfNoStreaming = (entity, select, streaming) => {
|
|
243
234
|
// might also be singleton w/o keys
|
|
244
235
|
if (!streaming && entity.keys) {
|
|
@@ -340,6 +331,7 @@ const _handleApply = (apply, select) => {
|
|
|
340
331
|
* @param {object} odataReq - OKRA's req
|
|
341
332
|
* @private
|
|
342
333
|
*/
|
|
334
|
+
// eslint-disable-next-line complexity
|
|
343
335
|
const readToCQN = (service, target, odataReq) => {
|
|
344
336
|
const uriInfo = odataReq.getUriInfo()
|
|
345
337
|
const segments = uriInfo.getPathSegments()
|
|
@@ -379,7 +371,7 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
379
371
|
select.push(...expand)
|
|
380
372
|
}
|
|
381
373
|
|
|
382
|
-
const isView =
|
|
374
|
+
const isView = target.params && Object.keys(target.params).length > 0
|
|
383
375
|
const kind = segments[segments.length - 1].getKind()
|
|
384
376
|
const isCollectionOrToMany = _isCollectionOrToMany(kind)
|
|
385
377
|
|
|
@@ -388,11 +380,10 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
388
380
|
|
|
389
381
|
// keep target as input because of localized view
|
|
390
382
|
const cqn = SELECT.from(isView ? _convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
|
|
391
|
-
addValidationQueryIfRequired(segments, isView, cqn, service, kind)
|
|
392
383
|
|
|
393
|
-
if (
|
|
394
|
-
|
|
395
|
-
|
|
384
|
+
if (isCollectionOrToMany && queryOptions && queryOptions[QueryOptions.COUNT]) cqn.SELECT.count = true
|
|
385
|
+
|
|
386
|
+
if (Object.keys(apply).length) _extendCqnWithApply(cqn, apply, entity)
|
|
396
387
|
|
|
397
388
|
if (isCollectionOrToMany || _isCount(kind)) {
|
|
398
389
|
_filter(service.model, entity, uriInfo, apply, cqn)
|
|
@@ -404,13 +395,13 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
404
395
|
_orderby(uriInfo, cqn)
|
|
405
396
|
}
|
|
406
397
|
|
|
407
|
-
if (!isCollectionOrToMany || entity._isSingleton)
|
|
408
|
-
cqn.SELECT.one = true
|
|
409
|
-
}
|
|
398
|
+
if (!isCollectionOrToMany || entity._isSingleton) cqn.SELECT.one = true
|
|
410
399
|
|
|
411
400
|
_cleanupForApply(apply, cqn)
|
|
401
|
+
|
|
412
402
|
// just like in new parser
|
|
413
403
|
if (cqn.SELECT.columns.length === 1 && cqn.SELECT.columns[0] === '*') delete cqn.SELECT.columns
|
|
404
|
+
|
|
414
405
|
return cqn
|
|
415
406
|
}
|
|
416
407
|
|