@sap/cds 6.8.4 → 7.0.1
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 +66 -4
- 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 +3 -3
- 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 +2 -2
- 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/protocols/odata-v4.js +9 -4
- 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/uri/UriTokenizer.js +5 -8
- 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/server.js +1 -1
- 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
|
@@ -7,6 +7,7 @@ const ResourceKind = odata.uri.UriResource.ResourceKind
|
|
|
7
7
|
const EdmPrimitiveTypeKind = odata.edm.EdmPrimitiveTypeKind
|
|
8
8
|
const { getFeatureNotSupportedError } = require('../../../util/errors')
|
|
9
9
|
const { getSegmentKeyValue } = require('./utils')
|
|
10
|
+
const normalizeTimestamp = require('../../../../common/utils/normalizeTimestamp')
|
|
10
11
|
|
|
11
12
|
const _binaryOperatorToCQN = new Map([
|
|
12
13
|
[BinaryOperatorKind.EQ, '='],
|
|
@@ -46,10 +47,10 @@ class ExpressionToCQN {
|
|
|
46
47
|
return { val: parseFloat(value) }
|
|
47
48
|
case EdmPrimitiveTypeKind.DateTimeOffset: {
|
|
48
49
|
try {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
return { val }
|
|
50
|
+
if (expression._cdsType === 'cds.DateTime')
|
|
51
|
+
// cut off ms if cds.DateTime
|
|
52
|
+
return { val: new Date(value).toISOString().replace(/\.\d\d\dZ$/, 'Z') }
|
|
53
|
+
return { val: normalizeTimestamp(value) }
|
|
53
54
|
} catch (e) {
|
|
54
55
|
throw Object.assign(new Error(`The type 'Edm.DateTimeOffset' is not compatible with '${value}'`), {
|
|
55
56
|
status: 400
|
|
@@ -6,7 +6,13 @@ const orderByToCQN = require('./orderByToCQN')
|
|
|
6
6
|
const selectToCQN = require('./selectToCQN')
|
|
7
7
|
const searchToCQN = require('./searchToCQN')
|
|
8
8
|
const applyToCQN = require('./applyToCQN')
|
|
9
|
-
const {
|
|
9
|
+
const {
|
|
10
|
+
convertUrlPathToCqn,
|
|
11
|
+
convertUrlPathToViewCqn,
|
|
12
|
+
getAllKeys,
|
|
13
|
+
isPathSupported,
|
|
14
|
+
getPropertyParam
|
|
15
|
+
} = require('./utils')
|
|
10
16
|
|
|
11
17
|
const QueryOptions = require('../okra/odata-server').QueryOptions
|
|
12
18
|
const {
|
|
@@ -33,11 +39,12 @@ const SUPPORTED_SEGMENT_KINDS = {
|
|
|
33
39
|
}
|
|
34
40
|
|
|
35
41
|
const expandToCQN = require('./expandToCQN')
|
|
36
|
-
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
37
42
|
const { isStreaming } = require('../utils/stream')
|
|
38
43
|
|
|
39
44
|
const { getPageSize } = require('../../../../common/generic/paging')
|
|
40
45
|
const { isAsteriskColumn } = require('../../../../common/utils/rewriteAsterisks')
|
|
46
|
+
const { handleStreamProperties } = require('../../../../common/utils/streamProp')
|
|
47
|
+
const { isNewStream } = require('../../../../common/utils/stream')
|
|
41
48
|
|
|
42
49
|
const { ensureUnlocalized } = require('../../../../fiori/utils/handler')
|
|
43
50
|
|
|
@@ -145,18 +152,6 @@ const _topSkip = (queryOptions, target, cqn) => {
|
|
|
145
152
|
}
|
|
146
153
|
}
|
|
147
154
|
|
|
148
|
-
const _getPropertyParam = pathSegments => {
|
|
149
|
-
const index = pathSegments[pathSegments.length - 1].getKind() === VALUE ? 2 : 1
|
|
150
|
-
const prop = pathSegments[pathSegments.length - index].getProperty()
|
|
151
|
-
const name = prop && prop.getName()
|
|
152
|
-
return (
|
|
153
|
-
name &&
|
|
154
|
-
(pathSegments.length > 1
|
|
155
|
-
? { ref: resolveStructuredName(pathSegments, pathSegments.length - 2, [name]) }
|
|
156
|
-
: { ref: [name] })
|
|
157
|
-
)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
155
|
const _isCollectionOrToMany = kind => {
|
|
161
156
|
return kind === ENTITY_COLLECTION || kind === NAVIGATION_TO_MANY
|
|
162
157
|
}
|
|
@@ -249,29 +244,6 @@ const _addKeysToSelectIfNoStreaming = (entity, select, streaming) => {
|
|
|
249
244
|
}
|
|
250
245
|
}
|
|
251
246
|
|
|
252
|
-
const _convertUrlPathToViewCqn = segments => {
|
|
253
|
-
const args = segments[0].getKeyPredicates().reduce((prev, curr) => {
|
|
254
|
-
const { keyName, val } = getSegmentKeyValue(curr)
|
|
255
|
-
prev[keyName.replace(/\//g, '_')] = { val }
|
|
256
|
-
return prev
|
|
257
|
-
}, {})
|
|
258
|
-
|
|
259
|
-
// REVISIT: Replace .getFullQualifiedName().toString() with findCsnTargetFor as done in convertUrlPathToCqn
|
|
260
|
-
return {
|
|
261
|
-
ref: [
|
|
262
|
-
{
|
|
263
|
-
id: segments[0]
|
|
264
|
-
.getEntitySet()
|
|
265
|
-
.getEntityType()
|
|
266
|
-
.getFullQualifiedName()
|
|
267
|
-
.toString()
|
|
268
|
-
.replace(/Parameters$/, ''),
|
|
269
|
-
args
|
|
270
|
-
}
|
|
271
|
-
]
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
|
|
275
247
|
const _expandRecursive = (ref, entity, expands = []) => {
|
|
276
248
|
if (ref.length > 1) {
|
|
277
249
|
let innerExpandElement = expands.find(e => e.ref[0] === ref[0])
|
|
@@ -344,12 +316,35 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
344
316
|
const segments = uriInfo.getPathSegments()
|
|
345
317
|
isPathSupported(SUPPORTED_SEGMENT_KINDS, segments)
|
|
346
318
|
|
|
319
|
+
if (isNewStream()) {
|
|
320
|
+
let propertyParam
|
|
321
|
+
if (isStreaming(segments)) {
|
|
322
|
+
propertyParam = getPropertyParam(segments)
|
|
323
|
+
} else if (segments[segments.length - 1]._isStreamByDollarValue) {
|
|
324
|
+
// REVISIT: Issue with multiple streaming properties ? Also in read.js
|
|
325
|
+
for (const k in target.elements) {
|
|
326
|
+
if (target.elements[k]['@Core.MediaType']) {
|
|
327
|
+
propertyParam = { ref: [k] }
|
|
328
|
+
break
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (propertyParam) {
|
|
334
|
+
const isView = target.params && Object.keys(target.params).length > 0
|
|
335
|
+
return STREAM.from(isView ? convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service)).column(
|
|
336
|
+
propertyParam.ref[0]
|
|
337
|
+
)
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
347
341
|
const queryOptions = odataReq.getQueryOptions()
|
|
348
342
|
const entity = service.model.definitions[ensureUnlocalized(target.name)]
|
|
349
|
-
const propertyParam =
|
|
343
|
+
const propertyParam = getPropertyParam(segments)
|
|
350
344
|
const apply = _apply(uriInfo, queryOptions, entity, service.model)
|
|
351
345
|
const select = _select(queryOptions, entity)
|
|
352
346
|
const expand = expandToCQN(uriInfo.getQueryOption(QueryOptions.EXPAND), uriInfo.getFinalEdmType(), service)
|
|
347
|
+
const streaming = isStreaming(segments)
|
|
353
348
|
|
|
354
349
|
if (Object.keys(apply).length) {
|
|
355
350
|
_handleApply(apply, select)
|
|
@@ -359,7 +354,7 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
359
354
|
select.push(propertyParam)
|
|
360
355
|
|
|
361
356
|
// add keys if no streaming, TODO: what if streaming via to-one
|
|
362
|
-
_addKeysToSelectIfNoStreaming(entity, select,
|
|
357
|
+
_addKeysToSelectIfNoStreaming(entity, select, streaming)
|
|
363
358
|
}
|
|
364
359
|
|
|
365
360
|
if (select.length === 0) {
|
|
@@ -378,7 +373,9 @@ const readToCQN = (service, target, odataReq) => {
|
|
|
378
373
|
_checkViewWithParamCall(isView, segments, kind, target.name)
|
|
379
374
|
|
|
380
375
|
// keep target as input because of localized view
|
|
381
|
-
const cqn = SELECT.from(isView ?
|
|
376
|
+
const cqn = SELECT.from(isView ? convertUrlPathToViewCqn(segments) : convertUrlPathToCqn(segments, service), select)
|
|
377
|
+
if (!cqn.SELECT.columns) cqn.SELECT.columns = ['*']
|
|
378
|
+
if (!streaming) handleStreamProperties(target, cqn, service.model, true)
|
|
382
379
|
|
|
383
380
|
const isCount =
|
|
384
381
|
isCollectionOrToMany &&
|
|
@@ -3,7 +3,8 @@ const { UPDATE } = cds.ql
|
|
|
3
3
|
|
|
4
4
|
const { getFeatureNotSupportedError } = require('../../../util/errors')
|
|
5
5
|
const { isStreaming } = require('../utils/stream')
|
|
6
|
-
const { convertUrlPathToCqn } = require('./utils')
|
|
6
|
+
const { convertUrlPathToCqn, getPropertyParam } = require('./utils')
|
|
7
|
+
const { isNewStream } = require('../../../../common/utils/stream')
|
|
7
8
|
|
|
8
9
|
const { ENTITY, NAVIGATION_TO_ONE, PRIMITIVE_PROPERTY, SINGLETON } =
|
|
9
10
|
require('../okra/odata-server').uri.UriResource.ResourceKind
|
|
@@ -28,6 +29,11 @@ const updateToCQN = (service, data, odataReq) => {
|
|
|
28
29
|
const segment = segments[segments.length - 1]
|
|
29
30
|
const streaming = isStreaming(segments)
|
|
30
31
|
|
|
32
|
+
if (isNewStream() && streaming) {
|
|
33
|
+
const col = getPropertyParam(segments).ref[0]
|
|
34
|
+
return STREAM.into(convertUrlPathToCqn(segments, service)).data(data[col]).column(col)
|
|
35
|
+
}
|
|
36
|
+
|
|
31
37
|
if (SUPPORTED_KINDS[segment.getKind()] || streaming) {
|
|
32
38
|
return UPDATE(convertUrlPathToCqn(segments, service)).data(data)
|
|
33
39
|
}
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
const { getFeatureNotSupportedError } = require('../../../util/errors')
|
|
2
|
-
|
|
2
|
+
const { resolveStructuredName } = require('../utils/handlerUtils')
|
|
3
3
|
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
4
4
|
|
|
5
|
-
const CDL_KEYWORDS = new Set(require('@sap/cds-compiler').to.cdl.keywords)
|
|
6
|
-
|
|
7
|
-
// TODO: Which EDM Types are missing?
|
|
8
|
-
const notToBeConvertedForCompiler = new Set([
|
|
9
|
-
'Edm.Boolean',
|
|
10
|
-
'Edm.Int16',
|
|
11
|
-
'Edm.Int32',
|
|
12
|
-
'Edm.Int64',
|
|
13
|
-
'Edm.Decimal',
|
|
14
|
-
'Edm.Double'
|
|
15
|
-
])
|
|
16
|
-
|
|
17
5
|
const addLimit = (item, rows, offset) => {
|
|
18
6
|
// ignore 0 offset -> truthy check
|
|
19
7
|
if (rows != null || offset) {
|
|
@@ -29,21 +17,49 @@ const addLimit = (item, rows, offset) => {
|
|
|
29
17
|
}
|
|
30
18
|
}
|
|
31
19
|
|
|
32
|
-
const
|
|
20
|
+
const _isNum = type => {
|
|
21
|
+
const nums = {
|
|
22
|
+
'Edm.Byte': 1,
|
|
23
|
+
'Edm.Int16': 1,
|
|
24
|
+
'Edm.Int32': 1,
|
|
25
|
+
'Edm.Double': 1
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return type.toString() in nums
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const _isBigNum = type => {
|
|
32
|
+
const bigNums = {
|
|
33
|
+
'Edm.Int64': 1,
|
|
34
|
+
'Edm.Decimal': 1
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return type.toString() in bigNums
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const _getValue = (value, type) => {
|
|
41
|
+
if (_isNum(type)) return Number(value)
|
|
42
|
+
if (_isBigNum(type)) {
|
|
43
|
+
const num = Number(value)
|
|
44
|
+
return Number.isSafeInteger(num) ? num : value
|
|
45
|
+
}
|
|
46
|
+
if (type === 'Edm.Boolean') return value === 'true'
|
|
47
|
+
return value
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const convertKeyPredicatesToWhere = keyPredicates => {
|
|
51
|
+
const where = []
|
|
33
52
|
if (keyPredicates.length) {
|
|
34
|
-
|
|
35
|
-
.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return `${CDL_KEYWORDS.has(keyName) ? `![${keyName}]` : keyName}=${keyValue}`
|
|
42
|
-
})
|
|
43
|
-
.join(' and ')}]`
|
|
53
|
+
keyPredicates.forEach(kp => {
|
|
54
|
+
if (where.length) where.push('and')
|
|
55
|
+
const keyName = kp.getEdmRef().getName().replace(/\//g, '.')
|
|
56
|
+
const type = kp.getEdmRef().getProperty().getType()
|
|
57
|
+
let keyValue = _getValue(kp.getText(), type.toString())
|
|
58
|
+
where.push({ ref: [keyName] }, '=', { val: keyValue })
|
|
59
|
+
})
|
|
44
60
|
}
|
|
45
61
|
|
|
46
|
-
return
|
|
62
|
+
return where
|
|
47
63
|
}
|
|
48
64
|
|
|
49
65
|
const convertUrlPathToCqn = (segments, service) => {
|
|
@@ -52,29 +68,74 @@ const convertUrlPathToCqn = (segments, service) => {
|
|
|
52
68
|
segment =>
|
|
53
69
|
segment.getKind() !== 'COUNT' && segment.getKind() !== 'PRIMITIVE.PROPERTY' && segment.getKind() !== 'VALUE'
|
|
54
70
|
)
|
|
55
|
-
.reduce((expr, segment
|
|
71
|
+
.reduce((expr, segment) => {
|
|
56
72
|
if (segment.getKind() === 'ENTITY' || segment.getKind() === 'ENTITY.COLLECTION') {
|
|
57
73
|
const entity = segment.getEntitySet().getEntityType().getFullQualifiedName()
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
74
|
+
const where = convertKeyPredicatesToWhere(segment.getKeyPredicates())
|
|
75
|
+
let ref
|
|
76
|
+
if (where.length) {
|
|
77
|
+
ref = { ref: [{ id: findCsnTargetFor(entity.name, service.model, service.namespace).name, where }] }
|
|
78
|
+
} else {
|
|
79
|
+
ref = { ref: [findCsnTargetFor(entity.name, service.model, service.namespace).name] }
|
|
80
|
+
}
|
|
81
|
+
return ref
|
|
61
82
|
}
|
|
62
83
|
|
|
63
84
|
if (segment.getKind() === 'SINGLETON') {
|
|
64
85
|
const singleton = segment.getSingleton().getEntityType().getFullQualifiedName()
|
|
65
|
-
|
|
66
|
-
return `${findCsnTargetFor(singleton.name, service.model, service.namespace).name}`
|
|
86
|
+
return { ref: [findCsnTargetFor(singleton.name, service.model, service.namespace).name] }
|
|
67
87
|
}
|
|
68
88
|
|
|
69
89
|
if (segment.getKind() === 'COMPLEX.PROPERTY') {
|
|
70
|
-
|
|
71
|
-
return
|
|
90
|
+
expr.ref.push(segment.getProperty().getName())
|
|
91
|
+
return expr
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const where = convertKeyPredicatesToWhere(segment.getKeyPredicates())
|
|
95
|
+
let ref
|
|
96
|
+
if (where.length) {
|
|
97
|
+
ref = { id: segment.getNavigationProperty().getName(), where }
|
|
98
|
+
} else {
|
|
99
|
+
ref = segment.getNavigationProperty().getName()
|
|
72
100
|
}
|
|
73
101
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return
|
|
77
|
-
},
|
|
102
|
+
if (!expr.ref) expr.ref = []
|
|
103
|
+
expr.ref.push(ref)
|
|
104
|
+
return expr
|
|
105
|
+
}, {})
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const convertUrlPathToViewCqn = segments => {
|
|
109
|
+
const args = segments[0].getKeyPredicates().reduce((prev, curr) => {
|
|
110
|
+
const { keyName, val } = getSegmentKeyValue(curr)
|
|
111
|
+
prev[keyName.replace(/\//g, '_')] = { val }
|
|
112
|
+
return prev
|
|
113
|
+
}, {})
|
|
114
|
+
|
|
115
|
+
// REVISIT: Replace .getFullQualifiedName().toString() with findCsnTargetFor as done in convertUrlPathToCqn
|
|
116
|
+
return {
|
|
117
|
+
ref: [
|
|
118
|
+
{
|
|
119
|
+
id: segments[0]
|
|
120
|
+
.getEntitySet()
|
|
121
|
+
.getEntityType()
|
|
122
|
+
.getFullQualifiedName()
|
|
123
|
+
.toString()
|
|
124
|
+
.replace(/Parameters$/, ''),
|
|
125
|
+
args
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const getPropertyParam = segments => {
|
|
132
|
+
const index = segments[segments.length - 1].getKind() === 1 ? 2 : 1
|
|
133
|
+
const prop = segments[segments.length - index].getProperty()
|
|
134
|
+
const name = prop && prop.getName()
|
|
135
|
+
return (
|
|
136
|
+
name &&
|
|
137
|
+
(segments.length > 1 ? { ref: resolveStructuredName(segments, segments.length - 2, [name]) } : { ref: [name] })
|
|
138
|
+
)
|
|
78
139
|
}
|
|
79
140
|
|
|
80
141
|
const isSameArray = (arr1, arr2) => {
|
|
@@ -143,8 +204,10 @@ const getSegmentKeyValue = segmentParam => {
|
|
|
143
204
|
module.exports = {
|
|
144
205
|
addLimit,
|
|
145
206
|
convertUrlPathToCqn,
|
|
207
|
+
convertUrlPathToViewCqn,
|
|
146
208
|
isSameArray,
|
|
147
209
|
getAllKeys,
|
|
148
210
|
isPathSupported,
|
|
149
|
-
getSegmentKeyValue
|
|
211
|
+
getSegmentKeyValue,
|
|
212
|
+
getPropertyParam
|
|
150
213
|
}
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/errors/AbstractError.js
CHANGED
|
@@ -35,7 +35,11 @@ class AbstractError extends Error {
|
|
|
35
35
|
setRootCause (rootCause) {
|
|
36
36
|
if (rootCause) {
|
|
37
37
|
if (!(rootCause instanceof Error)) {
|
|
38
|
-
|
|
38
|
+
if (typeof rootCause === 'object' && rootCause.message) {
|
|
39
|
+
rootCause = Object.assign(new Error(rootCause.message), rootCause)
|
|
40
|
+
} else {
|
|
41
|
+
throw new Error("Parameter 'rootCause' must be an instance of Error")
|
|
42
|
+
}
|
|
39
43
|
}
|
|
40
44
|
this._rootCause = rootCause
|
|
41
45
|
}
|
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const UriSyntaxError = require('../errors/UriSyntaxError')
|
|
4
4
|
|
|
5
|
-
const IDENTIFIER =
|
|
6
|
-
'(?:(?:_|\\p{Letter}|\\p{Letter_Number})' +
|
|
7
|
-
'(?:_|\\p{Letter}|\\p{Letter_Number}|\\p{Decimal_Number}' +
|
|
8
|
-
'|\\p{Nonspacing_Mark}|\\p{Spacing_Mark}|\\p{Connector_Punctuation}|\\p{Format}){0,127})'
|
|
5
|
+
const IDENTIFIER = '([_\\p{L}\\p{Nl}][_\\p{L}\\p{Nl}\\p{Nd}\\p{Mn}\\p{Mc}\\p{Pc}\\p{Cf}]{0,127})'
|
|
9
6
|
const IDENTIFIER_REGEXP = new RegExp('^' + IDENTIFIER, 'u')
|
|
10
|
-
const QUALIFIED_NAME_REGEXP = new RegExp('^' + IDENTIFIER + '(
|
|
7
|
+
const QUALIFIED_NAME_REGEXP = new RegExp('^' + IDENTIFIER + '(\\.' + IDENTIFIER + ')+', 'u')
|
|
11
8
|
const PARAMETER_ALIAS_NAME_REGEXP = new RegExp('^@' + IDENTIFIER, 'u')
|
|
12
9
|
|
|
13
10
|
const BOOLEAN_VALUE_REGEXP = new RegExp('^(?:true|false)', 'i')
|
|
@@ -696,9 +693,9 @@ class UriTokenizer {
|
|
|
696
693
|
* @private
|
|
697
694
|
*/
|
|
698
695
|
_nextWithRegularExpression (regexp) {
|
|
699
|
-
const
|
|
700
|
-
if (!
|
|
701
|
-
this._index +=
|
|
696
|
+
const matched = this._parseString.substring(this._index).match(regexp)
|
|
697
|
+
if (!matched) return false
|
|
698
|
+
this._index += matched[0].length
|
|
702
699
|
return true
|
|
703
700
|
}
|
|
704
701
|
|
package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/ValueConverter.js
CHANGED
|
@@ -336,7 +336,8 @@ class ValueConverter {
|
|
|
336
336
|
* output is requested in the formatParams parameter of the constructor of this class.
|
|
337
337
|
*/
|
|
338
338
|
convertDecimal (value, precision, scale) {
|
|
339
|
-
|
|
339
|
+
// now done in runtime's assert framework
|
|
340
|
+
// this._valueValidator.validateDecimal(value, precision, scale)
|
|
340
341
|
|
|
341
342
|
const bigValue = new big(value)
|
|
342
343
|
|
|
@@ -201,18 +201,19 @@ class ResourceJsonDeserializer {
|
|
|
201
201
|
// because the first parameter of a bound action is the binding parameter
|
|
202
202
|
// which is not part of the payload data.
|
|
203
203
|
if (edmAction.isBound()) parameters.shift()
|
|
204
|
-
|
|
204
|
+
|
|
205
|
+
if (parameters.length > 0) data = JSON.parse(value)
|
|
205
206
|
for (const [paramName, edmParam] of parameters) {
|
|
206
|
-
if (
|
|
207
|
-
throw new DeserializationError(
|
|
208
|
-
} else if (!data) {
|
|
209
|
-
data = JSON.parse(value)
|
|
210
|
-
if (typeof data !== 'object') {
|
|
211
|
-
throw new DeserializationError('Value for action parameters must be an object.')
|
|
212
|
-
}
|
|
207
|
+
if (!data && typeof data !== 'object') {
|
|
208
|
+
throw new DeserializationError('Value for action parameters must be an object.')
|
|
213
209
|
}
|
|
214
210
|
|
|
215
211
|
let paramValue = data[paramName]
|
|
212
|
+
|
|
213
|
+
if ((value === null || value === undefined || paramValue == null) && !edmParam.isNullable()) {
|
|
214
|
+
throw new DeserializationError(`Parameter '${paramName}' is not nullable`)
|
|
215
|
+
}
|
|
216
|
+
|
|
216
217
|
if (paramValue === undefined) {
|
|
217
218
|
// OData JSON Format Version 4.0 Plus Errata 03 - 17 Action Invocation:
|
|
218
219
|
// "Any parameter values not specified in the JSON object are assumed to have the null value."
|
|
@@ -21,7 +21,7 @@ function OkraAdapter(srv, model = srv.model) {
|
|
|
21
21
|
//
|
|
22
22
|
// REVISIT: Move to ExtensibilityService
|
|
23
23
|
//
|
|
24
|
-
if (cds.
|
|
24
|
+
if (cds.requires.extensibility || cds.requires.toggles)
|
|
25
25
|
module.exports = srv => {
|
|
26
26
|
const id = `${++unique} - ${srv.path}` // REVISIT: this is to allow running multiple express apps serving same endpoints, as done by some questionable tests
|
|
27
27
|
return function ODataAdapter(req, res) {
|
|
@@ -3,13 +3,15 @@ const {
|
|
|
3
3
|
} = require('../okra/odata-server')
|
|
4
4
|
|
|
5
5
|
const { findCsnTargetFor } = require('../../../../common/utils/csn')
|
|
6
|
-
const { isStreaming } = require('./stream')
|
|
7
6
|
const { convertStructured } = require('../../../../../common/utils/ucsn')
|
|
8
7
|
const { deepCopy } = require('../../../../common/utils/copy')
|
|
9
8
|
const { getSegmentKeyValue } = require('../odata-to-cqn/utils')
|
|
10
9
|
|
|
11
|
-
const _isFunctionInvocation =
|
|
12
|
-
|
|
10
|
+
const _isFunctionInvocation = odataReq => {
|
|
11
|
+
return (
|
|
12
|
+
odataReq.getUriInfo().getLastSegment().getFunction() || odataReq.getUriInfo().getLastSegment().getFunctionImport()
|
|
13
|
+
)
|
|
14
|
+
}
|
|
13
15
|
|
|
14
16
|
const _addStructuredProperties = ([structName, property, ...nestedProperties], paramData, value) => {
|
|
15
17
|
paramData[structName] = paramData[structName] || {}
|
|
@@ -129,14 +131,14 @@ function _entityOrTypeName(navSourceSegment) {
|
|
|
129
131
|
.getFullQualifiedName()
|
|
130
132
|
}
|
|
131
133
|
|
|
132
|
-
const _addForeignKeys = (service,
|
|
133
|
-
const pathSegments =
|
|
134
|
+
const _addForeignKeys = (service, odataReq, data) => {
|
|
135
|
+
const pathSegments = odataReq.getUriInfo().getPathSegments()
|
|
134
136
|
// retrieve keys/values from the path segment representing the navigation source
|
|
135
137
|
const navSourceSegment = pathSegments[pathSegments.length - 2]
|
|
136
138
|
const navSourceKeyValues = _getParamData(navSourceSegment.getKeyPredicates())
|
|
137
139
|
|
|
138
140
|
// retrieve relevant foreign key properties of the target entity, including the corresponding source key properties
|
|
139
|
-
const navProperty =
|
|
141
|
+
const navProperty = odataReq.getUriInfo().getLastSegment().getNavigationProperty()
|
|
140
142
|
|
|
141
143
|
// REVISIT: cannot be removed yet because of navigation of draft to non draft would add IsActiveEntity to .data
|
|
142
144
|
if (navProperty.getPartner() && navProperty.getPartner().getReferentialConstraints().size) {
|
|
@@ -179,10 +181,10 @@ const _getFunctionParameters = (lastSegment, keyValues, service, target) => {
|
|
|
179
181
|
return paramValues
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
const _getCopiedData = (odataReq,
|
|
184
|
+
const _getCopiedData = (odataReq, lastSegment, service, target) => {
|
|
183
185
|
let data = odataReq.getBody() || {}
|
|
184
186
|
|
|
185
|
-
if (
|
|
187
|
+
if (lastSegment.getKind() === 'PRIMITIVE.PROPERTY') {
|
|
186
188
|
return { [lastSegment.getProperty().getName()]: data }
|
|
187
189
|
}
|
|
188
190
|
|
|
@@ -219,8 +221,10 @@ const _getCopiedData = (odataReq, streaming, lastSegment, service, target) => {
|
|
|
219
221
|
const getData = (component, odataReq, service, target) => {
|
|
220
222
|
const segments = odataReq.getUriInfo().getPathSegments()
|
|
221
223
|
const lastSegment = odataReq.getUriInfo().getLastSegment()
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
+
const keyPredicates =
|
|
225
|
+
lastSegment.getKind() === 'PRIMITIVE.PROPERTY'
|
|
226
|
+
? segments[segments.length - 2].getKeyPredicates()
|
|
227
|
+
: lastSegment.getKeyPredicates()
|
|
224
228
|
const keyValues = _getParamData(keyPredicates)
|
|
225
229
|
|
|
226
230
|
if (component === DATA_READ_HANDLER && _isFunctionInvocation(odataReq)) {
|
|
@@ -235,7 +239,7 @@ const getData = (component, odataReq, service, target) => {
|
|
|
235
239
|
}
|
|
236
240
|
|
|
237
241
|
// copy so that original payload is preserved
|
|
238
|
-
const data = _getCopiedData(odataReq,
|
|
242
|
+
const data = _getCopiedData(odataReq, lastSegment, service, target)
|
|
239
243
|
|
|
240
244
|
// Only to be done for post via navigation
|
|
241
245
|
if (
|
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
const { getDeepSelect, getSimpleSelectCQN } = require('./handlerUtils')
|
|
9
9
|
const { hasDeepUpdate } = require('../../../../common/composition/update')
|
|
10
10
|
const { WRITE_EVENTS, CDS_EVENTS } = require('../../../../common/constants/events')
|
|
11
|
+
const { addEtagColumns } = require('../../../../common/utils/etag')
|
|
11
12
|
|
|
12
13
|
const setLocationHeader = (req, { model }) => {
|
|
13
14
|
const { odataRes } = req._
|
|
@@ -67,6 +68,9 @@ const readAfterWrite = async (req, srv, { operation, isBefore } = { isBefore: fa
|
|
|
67
68
|
Object.defineProperty(query.SELECT, '_4odata', { value: true })
|
|
68
69
|
_ensureKeysAreSelected(query)
|
|
69
70
|
|
|
71
|
+
// automatically add etag columns if not already there
|
|
72
|
+
addEtagColumns(query.SELECT.columns, req.target)
|
|
73
|
+
|
|
70
74
|
// gracefully set location and no body if no read auth or not readable capability
|
|
71
75
|
let result
|
|
72
76
|
try {
|
|
@@ -7,6 +7,7 @@ const getTemplate = require('../../../../common/utils/template')
|
|
|
7
7
|
const templateProcessor = require('../../../../common/utils/templateProcessor')
|
|
8
8
|
const { omitValue, applyOmitValuesPreference } = require('./omitValues')
|
|
9
9
|
const { setLocationHeader } = require('./readAfterWrite')
|
|
10
|
+
const normalizeTimestamp = require('../../../../common/utils/normalizeTimestamp')
|
|
10
11
|
|
|
11
12
|
const METADATA = {
|
|
12
13
|
$context: '*@odata.context',
|
|
@@ -210,6 +211,7 @@ const _processCategory = (req, category, elementInfo, options, previousResult) =
|
|
|
210
211
|
|
|
211
212
|
case '@cds.Boolean':
|
|
212
213
|
if (row[key] != null) row[key] = !!row[key]
|
|
214
|
+
break
|
|
213
215
|
|
|
214
216
|
// no default
|
|
215
217
|
}
|
|
@@ -273,6 +275,7 @@ const _pick = options => (element, target) => {
|
|
|
273
275
|
|
|
274
276
|
if (element['@odata.etag']) categories.push('@odata.etag')
|
|
275
277
|
if (element._type === 'cds.Decimal') categories.push('@cds.Decimal')
|
|
278
|
+
if (element._type === 'cds.Timestamp') categories.push('@cds.Timestamp')
|
|
276
279
|
if (cds.db?.cqn2sql && element._type === 'cds.Boolean') categories.push('@cds.Boolean') // REVISIT: violates modularization -> do we still need that?
|
|
277
280
|
|
|
278
281
|
categories.push(..._assocs(element, target))
|
|
@@ -373,8 +376,8 @@ const postProcessMinimal = (req, service, result) => {
|
|
|
373
376
|
setLocationHeader(req, service)
|
|
374
377
|
|
|
375
378
|
const etag = target._etag && target._etag.name
|
|
376
|
-
if (
|
|
377
|
-
return { '*@odata.etag': result[etag] === '$now' ?
|
|
379
|
+
if (etag && etag in result) {
|
|
380
|
+
return { '*@odata.etag': result[etag] === '$now' ? normalizeTimestamp(timestamp) : result[etag] }
|
|
378
381
|
}
|
|
379
382
|
|
|
380
383
|
return null
|