@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
|
@@ -67,8 +67,8 @@ const convertPathExpressionToWhere = (fromClause, model, options) => {
|
|
|
67
67
|
const currentEntityName = draft ? entityName && ensureDraftsSuffix(entityName) : entityName
|
|
68
68
|
if (!currentEntityName) continue
|
|
69
69
|
if (!draft && currentEntityName.endsWith('_drafts')) draft = true
|
|
70
|
-
const tableAlias = `T${i}`
|
|
71
|
-
const currentSelect = SELECT.from(
|
|
70
|
+
const tableAlias = options.tableAliasPrefix ? `${options.tableAliasPrefix}${i}` : `T${i}`
|
|
71
|
+
const currentSelect = SELECT.from({ ref: [currentEntityName], as: tableAlias })
|
|
72
72
|
|
|
73
73
|
if (fromClause.ref[i].where) {
|
|
74
74
|
currentSelect.where(addAliasToExpression(fromClause.ref[i].where, tableAlias))
|
|
@@ -969,13 +969,16 @@ const _convertUpdate = (query, model, options) => {
|
|
|
969
969
|
|
|
970
970
|
if (alias) update.UPDATE.entity = { ref: [target], as: alias }
|
|
971
971
|
if (where) update.where(where)
|
|
972
|
+
|
|
972
973
|
const targetEntity = model.definitions[target]
|
|
974
|
+
|
|
973
975
|
if (query.UPDATE.where) {
|
|
974
976
|
update.where(addAliasToExpression(query.UPDATE.where, alias))
|
|
975
977
|
_convertToOneEqNullInFilter(update.UPDATE, targetEntity)
|
|
976
978
|
}
|
|
977
979
|
|
|
978
980
|
if (!targetEntity) return update
|
|
981
|
+
|
|
979
982
|
return resolveView(update, model, cds.db)
|
|
980
983
|
}
|
|
981
984
|
|
|
@@ -39,56 +39,6 @@ const _getUps = (entity, model) => {
|
|
|
39
39
|
return entity.set('__parents', ups)
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
const _ifDataSubject = (entity, role) => {
|
|
43
|
-
return entity['@PersonalData.EntitySemantics'] === 'DataSubject' && entity['@PersonalData.DataSubjectRole'] === role
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const _getDataSubjectUp = (role, model, entity, prev, next, result) => {
|
|
47
|
-
for (const element of _getUps(entity, model)) {
|
|
48
|
-
const me = { entity, relative: element.parent, element }
|
|
49
|
-
if (prev) prev.next = me
|
|
50
|
-
if (_ifDataSubject(element.parent, role)) {
|
|
51
|
-
if (!result) result = { dataSubjectEntity: element.parent, subs: [] }
|
|
52
|
-
result.subs.push(next || me)
|
|
53
|
-
return result
|
|
54
|
-
} else {
|
|
55
|
-
// dfs is a must here
|
|
56
|
-
result = _getDataSubjectUp(role, model, element.parent, me, next || me, result)
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return result
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
const _getDataSubjectDown = (role, entity, prev, next) => {
|
|
63
|
-
const associations = Object.values(entity.associations || {}).filter(e => !e._isBacklink)
|
|
64
|
-
for (const element of associations) {
|
|
65
|
-
const me = { entity, relative: entity, element }
|
|
66
|
-
if (_ifDataSubject(element._target, role)) {
|
|
67
|
-
if (prev) prev.next = me
|
|
68
|
-
return { dataSubjectEntity: element._target, subs: [next || me] }
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
// bfs makes more sense here
|
|
72
|
-
for (const element of associations) {
|
|
73
|
-
const me = { entity, relative: entity, element }
|
|
74
|
-
if (prev) prev.next = me
|
|
75
|
-
const dataSubject = _getDataSubjectDown(role, element._target, me, next || me)
|
|
76
|
-
if (dataSubject) return dataSubject
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const getDataSubject = (entity, model, role) => {
|
|
81
|
-
const hash = '__dataSubject4' + role
|
|
82
|
-
if (entity.own(hash)) return entity[hash]
|
|
83
|
-
// entities with EntitySemantics 'DataSubjectDetails' or 'Other' must not necessarily
|
|
84
|
-
// be always below or always above 'DataSubject' entity in CSN tree
|
|
85
|
-
let dataSubject = _getDataSubjectUp(role, model, entity)
|
|
86
|
-
if (!dataSubject) {
|
|
87
|
-
dataSubject = _getDataSubjectDown(role, entity)
|
|
88
|
-
}
|
|
89
|
-
return entity.set(hash, dataSubject)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
42
|
const _resolve = (edmName, model, namespace) => {
|
|
93
43
|
const resolved = model._edmToCSNNameMap[namespace][edmName.replace(/\./g, '_')]
|
|
94
44
|
// the edm name has an additional suffix 'Parameters' in case of views with parameters
|
|
@@ -249,7 +199,6 @@ module.exports = {
|
|
|
249
199
|
getEtagElement,
|
|
250
200
|
findCsnTargetFor,
|
|
251
201
|
getElementDeep,
|
|
252
|
-
getDataSubject,
|
|
253
202
|
alias2ref,
|
|
254
203
|
getComp2oneParents,
|
|
255
204
|
prefixForStruct,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const { isAsteriskColumn } = require('./rewriteAsterisks')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Recursively adds etag columns if a manual list of columns is specified.
|
|
5
|
+
* If asterisk columns or no columns are given, the database layer will
|
|
6
|
+
* add the etag columns anyway.
|
|
7
|
+
*/
|
|
8
|
+
const addEtagColumns = (columns, entity) => {
|
|
9
|
+
if (!columns || !Array.isArray(columns)) return
|
|
10
|
+
if (
|
|
11
|
+
entity._etag &&
|
|
12
|
+
!columns.some(c => isAsteriskColumn(c)) &&
|
|
13
|
+
!(columns.length === 1 && columns[0].func === 'count') &&
|
|
14
|
+
!columns.some(c => c.ref && c.ref[c.ref.length - 1] === entity._etag.name)
|
|
15
|
+
) {
|
|
16
|
+
columns.push({ ref: [entity._etag.name] })
|
|
17
|
+
}
|
|
18
|
+
const expands = columns.filter(c => c.expand)
|
|
19
|
+
for (const expand of expands) {
|
|
20
|
+
const refName = expand.ref[expand.ref.length - 1]
|
|
21
|
+
const targetEntity = refName && entity.elements[refName] && entity.elements[refName]._target
|
|
22
|
+
if (targetEntity) {
|
|
23
|
+
addEtagColumns(expand.expand, targetEntity)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
addEtagColumns
|
|
30
|
+
}
|
|
@@ -92,7 +92,7 @@ function _getWhereFromUpdate(query, target, model) {
|
|
|
92
92
|
|
|
93
93
|
const where = query.UPDATE.where || []
|
|
94
94
|
if (query.UPDATE.entity.ref?.length === 1 && query.UPDATE.entity.ref[0].where)
|
|
95
|
-
return _mergeWhere(query.UPDATE.entity.ref[0].where, where)
|
|
95
|
+
return _mergeWhere(where.length ? [...query.UPDATE.entity.ref[0].where] : query.UPDATE.entity.ref[0].where, where)
|
|
96
96
|
return where
|
|
97
97
|
}
|
|
98
98
|
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
const PRECISION = cds.env.features.precise_timestamps ? 7 : 3
|
|
3
|
+
|
|
4
|
+
const TZ_REGEX = new RegExp(/(Z|[+-][01]\d:?[0-5]\d)$/)
|
|
5
|
+
const NON_DIGIT_REGEX = new RegExp(/\D/, 'g')
|
|
6
|
+
|
|
7
|
+
const _lengthIfNotFoundIndex = (index, length) => (index > -1 ? index : length)
|
|
8
|
+
|
|
9
|
+
module.exports = value => {
|
|
10
|
+
if (value instanceof Date) value = value.toISOString()
|
|
11
|
+
if (typeof value === 'number') value = new Date(value).toISOString()
|
|
12
|
+
|
|
13
|
+
const decimalPointIndex = _lengthIfNotFoundIndex(value.lastIndexOf('.'), value.length)
|
|
14
|
+
const tzRegexMatch = TZ_REGEX.exec(value)
|
|
15
|
+
const tz = tzRegexMatch?.[0] || ''
|
|
16
|
+
const tzIndex = _lengthIfNotFoundIndex(tzRegexMatch?.index, value.length)
|
|
17
|
+
const dateEndIndex = Math.min(decimalPointIndex, tzIndex)
|
|
18
|
+
const dateNoMillisNoTZ = new Date(value.slice(0, dateEndIndex) + tz).toISOString().slice(0, 19)
|
|
19
|
+
const normalizedFractionalDigits = value
|
|
20
|
+
.slice(dateEndIndex + 1, tzIndex)
|
|
21
|
+
.replace(NON_DIGIT_REGEX, '')
|
|
22
|
+
.padEnd(PRECISION, '0')
|
|
23
|
+
.slice(0, PRECISION)
|
|
24
|
+
return dateNoMillisNoTZ + (normalizedFractionalDigits ? '.' + normalizedFractionalDigits : '') + 'Z'
|
|
25
|
+
}
|
|
@@ -9,7 +9,7 @@ const getEntityFromPath = (path, def) => {
|
|
|
9
9
|
let id
|
|
10
10
|
for (const segment of path.ref) {
|
|
11
11
|
id = ensureNoDraftsSuffix(segment.id || segment)
|
|
12
|
-
current = current
|
|
12
|
+
current = current?.elements[id] || current
|
|
13
13
|
if (current && current.target) current = current._target
|
|
14
14
|
}
|
|
15
15
|
return current
|
|
@@ -217,6 +217,7 @@ const _newInsertColumns = (columns = [], transition) => {
|
|
|
217
217
|
return newColumns
|
|
218
218
|
}
|
|
219
219
|
|
|
220
|
+
// REVISIT: this hard-coding on ref indexes does not support path expressions
|
|
220
221
|
const _newWhereRef = (newWhereElement, transition, alias, tableName, isSubSelect) => {
|
|
221
222
|
const newRef = Array.isArray(newWhereElement.ref) ? [...newWhereElement.ref] : [newWhereElement.ref]
|
|
222
223
|
if (newRef[0] === alias) {
|
|
@@ -249,7 +250,7 @@ const _newWhere = (where = [], transition, tableName, alias, isSubselect = false
|
|
|
249
250
|
|
|
250
251
|
const newWhereElement = { ...whereElement }
|
|
251
252
|
if (!whereElement.ref && !whereElement.SELECT && !whereElement.func) return whereElement
|
|
252
|
-
if (whereElement.SELECT && whereElement.SELECT.where) {
|
|
253
|
+
if (whereElement.SELECT && whereElement.SELECT.where && !whereElement._doNotResolve) {
|
|
253
254
|
newWhereElement.SELECT.where = _newWhere(whereElement.SELECT.where, transition, tableName, alias, true)
|
|
254
255
|
return newWhereElement
|
|
255
256
|
} else {
|
|
@@ -6,8 +6,9 @@ const cds = require('../../cds')
|
|
|
6
6
|
|
|
7
7
|
const isAsteriskColumn = col => col === '*' || (col.ref && col.ref[0] === '*' && !col.expand)
|
|
8
8
|
|
|
9
|
-
const
|
|
10
|
-
if (newColumn.as
|
|
9
|
+
const isDuplicate = newColumn => column => {
|
|
10
|
+
if (newColumn.as && column.as) return column.as === newColumn.as
|
|
11
|
+
if ((newColumn.as && !column.as) || (!newColumn.as && column.as)) return
|
|
11
12
|
if (!column.ref) return
|
|
12
13
|
if (Array.isArray(newColumn)) newColumn = { ref: newColumn }
|
|
13
14
|
return newColumn.ref ? newColumn.ref.join('_') === column.ref.join('_') : newColumn === column.ref.join('_')
|
|
@@ -50,7 +51,7 @@ const _rewriteAsterisk = (columns, target, _4db, isRoot) => {
|
|
|
50
51
|
1,
|
|
51
52
|
...getColumns(target, { _4db })
|
|
52
53
|
.map(c => ({ ref: [c.name] }))
|
|
53
|
-
.filter(c => !columns.find(
|
|
54
|
+
.filter(c => !columns.find(isDuplicate(c)) && (isRoot || c.ref[0] !== 'DraftAdministrativeData_DraftUUID'))
|
|
54
55
|
)
|
|
55
56
|
}
|
|
56
57
|
}
|
|
@@ -136,5 +137,6 @@ const rewriteAsterisks = (query, model, options) => {
|
|
|
136
137
|
module.exports = {
|
|
137
138
|
rewriteAsterisks,
|
|
138
139
|
isAsteriskColumn,
|
|
139
|
-
rewriteExpandAsterisk
|
|
140
|
+
rewriteExpandAsterisk,
|
|
141
|
+
isDuplicate
|
|
140
142
|
}
|
|
@@ -12,30 +12,26 @@ const _targetFrom = (cqn, options) => {
|
|
|
12
12
|
const search2cqn4sql = (query, model, options = {}) => {
|
|
13
13
|
const cqnSearchPhrase = query.SELECT.search
|
|
14
14
|
if (!cqnSearchPhrase) return
|
|
15
|
+
|
|
15
16
|
const { search2cqn4sql } = options
|
|
16
17
|
const { entityName, alias } = _targetFrom(query.SELECT.from, options)
|
|
17
18
|
const entity = model.definitions[entityName]
|
|
18
|
-
const
|
|
19
|
+
const aggregated = query._aggregated || /* new parser */ query.SELECT.groupBy
|
|
20
|
+
|
|
19
21
|
// Call custom (optimized search to cqn for sql implementation) that tries
|
|
20
22
|
// to optimize the search behavior for a specific database service.
|
|
21
|
-
|
|
22
|
-
if (
|
|
23
|
-
typeof search2cqn4sql === 'function' &&
|
|
24
|
-
!query.SELECT.count &&
|
|
25
|
-
localizedAssociation &&
|
|
26
|
-
!(query._aggregated || /* new parser */ query.SELECT.groupBy)
|
|
27
|
-
) {
|
|
23
|
+
if (typeof search2cqn4sql === 'function' && entity.associations?.localized && !aggregated) {
|
|
28
24
|
const search2cqnOptions = { columns: computeColumnsToBeSearched(query, entity), locale: options.locale }
|
|
29
25
|
return search2cqn4sql(query, entity, search2cqnOptions)
|
|
30
|
-
} else {
|
|
31
|
-
const columnsToBeSearched = computeColumnsToBeSearched(query, entity, alias)
|
|
32
|
-
const expression = columnsToBeSearched?.length
|
|
33
|
-
? searchToLike(cqnSearchPhrase, columnsToBeSearched)
|
|
34
|
-
: [{ val: 0 }, '=', { val: 1 }]
|
|
35
|
-
|
|
36
|
-
// REVISIT: find out here if where or having must be used
|
|
37
|
-
query._aggregated || /* if new parser */ query.SELECT.groupBy ? query.having(expression) : query.where(expression)
|
|
38
26
|
}
|
|
27
|
+
|
|
28
|
+
const columnsToBeSearched = computeColumnsToBeSearched(query, entity, alias)
|
|
29
|
+
const expression = columnsToBeSearched?.length
|
|
30
|
+
? searchToLike(cqnSearchPhrase, columnsToBeSearched)
|
|
31
|
+
: [{ val: 0 }, '=', { val: 1 }]
|
|
32
|
+
|
|
33
|
+
// REVISIT: find out here if where or having must be used
|
|
34
|
+
aggregated ? query.having(expression) : query.where(expression)
|
|
39
35
|
}
|
|
40
36
|
|
|
41
37
|
module.exports = search2cqn4sql
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const cds = require('../../cds')
|
|
2
|
+
const LOG = cds.log('odata')
|
|
3
|
+
const { SELECT } = cds.ql
|
|
4
|
+
const { deepCopyArray } = require('./copy')
|
|
5
|
+
const { getTransition } = require('./resolveView')
|
|
6
|
+
const { cqn2cqn4sql } = require('./cqn2cqn4sql')
|
|
7
|
+
const getTemplate = require('./template')
|
|
8
|
+
const templateProcessor = require('./templateProcessor')
|
|
9
|
+
const { adaptStreamCQN } = require('../../fiori/utils/stream.js')
|
|
10
|
+
const { isPathToDraft } = require('./cqn')
|
|
11
|
+
|
|
12
|
+
// eslint-disable-next-line complexity
|
|
13
|
+
const _getStreamProperties = (req, query, model) => {
|
|
14
|
+
// new odata parser sets streaming property in SELECT.from
|
|
15
|
+
const ref = query.SELECT
|
|
16
|
+
? (query.SELECT.columns && query.SELECT.columns[0].ref) || query.SELECT.from.ref
|
|
17
|
+
: [query.STREAM.column]
|
|
18
|
+
const propertyName = ref[ref.length - 1]
|
|
19
|
+
let mediaTypeProperty
|
|
20
|
+
for (let key in req.target.elements) {
|
|
21
|
+
const val = req.target.elements[key]
|
|
22
|
+
if (val['@Core.MediaType'] && val.name === propertyName) {
|
|
23
|
+
mediaTypeProperty = val
|
|
24
|
+
break
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let contentType, contentDispositionFilename
|
|
29
|
+
const columns = []
|
|
30
|
+
if (typeof mediaTypeProperty['@Core.MediaType'] === 'object') {
|
|
31
|
+
let contentTypeProperty = mediaTypeProperty['@Core.MediaType']['=']
|
|
32
|
+
if (!req.target.elements[contentTypeProperty]) {
|
|
33
|
+
LOG._warn &&
|
|
34
|
+
LOG.warn(
|
|
35
|
+
`@Core.MediaType in entity "${req.target.name}" points to property "${contentTypeProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
|
|
36
|
+
)
|
|
37
|
+
const mapping = getTransition(req.target, cds.db).mapping
|
|
38
|
+
const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === contentTypeProperty)
|
|
39
|
+
contentTypeProperty = key && key.length && key[0]
|
|
40
|
+
}
|
|
41
|
+
if (!req.target.elements[contentTypeProperty]) {
|
|
42
|
+
LOG._warn && LOG.warn(`MediaType ${contentTypeProperty} not found in entity "${req.target.name}".`)
|
|
43
|
+
} else {
|
|
44
|
+
columns.push({ ref: [contentTypeProperty], as: 'contentType' })
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
contentType = mediaTypeProperty['@Core.MediaType']
|
|
48
|
+
}
|
|
49
|
+
if (mediaTypeProperty['@Core.ContentDisposition.Filename']) {
|
|
50
|
+
if (typeof mediaTypeProperty['@Core.ContentDisposition.Filename'] === 'object') {
|
|
51
|
+
let contentDispositionProperty = mediaTypeProperty['@Core.ContentDisposition.Filename']['=']
|
|
52
|
+
if (!req.target.elements[contentDispositionProperty]) {
|
|
53
|
+
LOG._warn &&
|
|
54
|
+
LOG.warn(
|
|
55
|
+
`@Core.ContentDisposition.Filename in entity "${req.target.name}" points to property "${contentDispositionProperty}" which was renamed or is not part of the projection. You must update the annotation value.`
|
|
56
|
+
)
|
|
57
|
+
const mapping = getTransition(req.target, cds.db).mapping
|
|
58
|
+
const key = [...mapping.entries()].find(({ 1: val }) => val.ref[0] === contentDispositionProperty)
|
|
59
|
+
contentDispositionProperty = key && key.length && key[0]
|
|
60
|
+
}
|
|
61
|
+
if (!req.target.elements[contentDispositionProperty]) {
|
|
62
|
+
LOG._warn &&
|
|
63
|
+
LOG.warn(`ContentDisposition ${contentDispositionProperty} not found in entity "${req.target.name}".`)
|
|
64
|
+
} else {
|
|
65
|
+
columns.push({ ref: [contentDispositionProperty], as: 'contentDispositionFilename' })
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
contentDispositionFilename = mediaTypeProperty['@Core.ContentDisposition.Filename']
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
const contentDispositionType = mediaTypeProperty['@Core.ContentDisposition.Type']
|
|
72
|
+
|
|
73
|
+
if (columns.length && cds.db && !req.target._hasPersistenceSkip) {
|
|
74
|
+
// used cloned path
|
|
75
|
+
const ref = query.SELECT ? query.SELECT.from.ref : query.STREAM.from.ref
|
|
76
|
+
const as = query.SELECT ? query.SELECT.from.as : query.STREAM.from.as
|
|
77
|
+
let select = SELECT.one.from({ ref: deepCopyArray(ref), as }).columns(columns)
|
|
78
|
+
const where = query.SELECT ? query.SELECT.where : query.STREAM.where
|
|
79
|
+
if (where?.length) select.SELECT.where = where
|
|
80
|
+
if (!(isNewStream() || cds.env.fiori.lean_draft)) {
|
|
81
|
+
const draft = req.target._isDraftEnabled && isPathToDraft(select.SELECT.from.ref, model)
|
|
82
|
+
if (draft) {
|
|
83
|
+
select = cqn2cqn4sql(select, model)
|
|
84
|
+
adaptStreamCQN(select, draft)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return cds
|
|
89
|
+
.tx(req)
|
|
90
|
+
.run(select)
|
|
91
|
+
.then(res => ({
|
|
92
|
+
contentType: (res && res.contentType) || contentType,
|
|
93
|
+
contentDispositionFilename: (res && res.contentDispositionFilename) || contentDispositionFilename,
|
|
94
|
+
contentDispositionType
|
|
95
|
+
}))
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return Promise.resolve({ contentType, contentDispositionFilename, contentDispositionType })
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const enhanceStreamResult = async (req, query, result, model) => {
|
|
102
|
+
if (!result) return
|
|
103
|
+
if (result.$mediaContentType || result.$mediaContentDispositionFilename || result.$mediaContentDispositionType) return
|
|
104
|
+
|
|
105
|
+
const { contentType, contentDispositionFilename, contentDispositionType } = await _getStreamProperties(
|
|
106
|
+
req,
|
|
107
|
+
query,
|
|
108
|
+
model
|
|
109
|
+
)
|
|
110
|
+
if (contentType) result.$mediaContentType = contentType
|
|
111
|
+
if (contentDispositionFilename) {
|
|
112
|
+
result.$mediaContentDispositionFilename = contentDispositionFilename
|
|
113
|
+
if (contentDispositionType) result.$mediaContentDispositionType = contentDispositionType
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const pick = element => {
|
|
118
|
+
return element['@Core.IsURL']
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const processFn = ({ row, key }) => {
|
|
122
|
+
row[`${key}@odata.mediaReadLink`] = row[key]
|
|
123
|
+
delete row[key]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function transformRedirectProperties(req, service, result) {
|
|
127
|
+
if (!Array.isArray(result)) result = [result]
|
|
128
|
+
if (result.length === 0) return
|
|
129
|
+
|
|
130
|
+
const template = getTemplate('redirect-properties', service, req.target, { pick })
|
|
131
|
+
if (template && template.elements.size) {
|
|
132
|
+
for (const row of result) {
|
|
133
|
+
templateProcessor({ processFn, row, template })
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const isNewStream = () => cds.env.features.new_stream && (!cds.db || !!cds.db.stream('foo').STREAM)
|
|
139
|
+
|
|
140
|
+
module.exports = { isNewStream, enhanceStreamResult, transformRedirectProperties }
|
|
@@ -1,21 +1,38 @@
|
|
|
1
1
|
const { ensureNoDraftsSuffix, ensureUnlocalized } = require('../../fiori/utils/handler')
|
|
2
|
+
const { isDuplicate } = require('./rewriteAsterisks')
|
|
3
|
+
|
|
4
|
+
const _addColumn = (name, type, columns) => {
|
|
5
|
+
if (typeof type === 'object') {
|
|
6
|
+
const ref = {
|
|
7
|
+
ref: [type['=']],
|
|
8
|
+
as: `${name}@odata.mediaContentType`
|
|
9
|
+
}
|
|
10
|
+
if (!columns.find(isDuplicate(ref))) columns.push(ref)
|
|
11
|
+
} else {
|
|
12
|
+
const val = { val: type, as: `${name}@odata.mediaContentType` }
|
|
13
|
+
if (!columns.find(isDuplicate(val))) columns.push(val)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
2
16
|
|
|
3
17
|
const _changeStreamProperties = (target, columns, model) => {
|
|
4
|
-
|
|
18
|
+
let index = columns.length
|
|
19
|
+
while (index--) {
|
|
5
20
|
const col = columns[index]
|
|
6
21
|
const name = col.ref && col.ref[col.ref.length - 1]
|
|
7
22
|
const element = name && target.elements[name]
|
|
8
|
-
const type = element &&
|
|
23
|
+
const type = element && element['@Core.MediaType']
|
|
9
24
|
|
|
10
|
-
if (col
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
25
|
+
if (col === '*') {
|
|
26
|
+
for (const k in target.elements) {
|
|
27
|
+
const el = target.elements[k]
|
|
28
|
+
if (el['@Core.MediaType']) {
|
|
29
|
+
const type = el['@Core.MediaType']
|
|
30
|
+
_addColumn(el.name, type, columns)
|
|
15
31
|
}
|
|
16
|
-
} else {
|
|
17
|
-
columns[index] = { val: type, as: `${name}@odata.mediaContentType` }
|
|
18
32
|
}
|
|
33
|
+
} else if (col.ref && type) {
|
|
34
|
+
_addColumn(name, type, columns)
|
|
35
|
+
if (!element['@Core.IsURL']) columns.splice(index, 1)
|
|
19
36
|
} else if (col.expand && col.ref) {
|
|
20
37
|
const tgt = target.elements[col.ref] && target.elements[col.ref].target
|
|
21
38
|
tgt && _changeStreamProperties(model.definitions[ensureUnlocalized(ensureNoDraftsSuffix(tgt))], col.expand, model)
|
|
@@ -23,10 +40,10 @@ const _changeStreamProperties = (target, columns, model) => {
|
|
|
23
40
|
}
|
|
24
41
|
}
|
|
25
42
|
|
|
26
|
-
const handleStreamProperties = (target, select, model) => {
|
|
27
|
-
const columns = select.SELECT
|
|
43
|
+
const handleStreamProperties = (target, select, model, _4odata) => {
|
|
44
|
+
const columns = select.SELECT?.columns
|
|
28
45
|
if (!columns || !target || !model) return
|
|
29
|
-
if (!select.SELECT._4odata) return
|
|
46
|
+
if (!_4odata && !select.SELECT._4odata) return
|
|
30
47
|
if (select._streaming) return
|
|
31
48
|
|
|
32
49
|
_changeStreamProperties(target, columns, model)
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
// before
|
|
2
2
|
const rewrite = require('./rewrite')
|
|
3
3
|
const input = require('./input')
|
|
4
|
-
const integrity = require('./integrity')
|
|
5
4
|
const { convertVirtuals: virtual } = require('./virtual')
|
|
6
5
|
// on
|
|
7
6
|
const CREATE = require('./create')
|
|
@@ -18,7 +17,6 @@ module.exports = {
|
|
|
18
17
|
rewrite,
|
|
19
18
|
virtual,
|
|
20
19
|
input,
|
|
21
|
-
integrity,
|
|
22
20
|
CREATE,
|
|
23
21
|
READ,
|
|
24
22
|
UPDATE,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const { getFlatArray, processCQNs } = require('../utils/deep')
|
|
2
|
-
const
|
|
2
|
+
const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
|
|
3
3
|
const { hasDeepDelete, getDeepDeleteCQNs, getSetNullParentForeignKeyCQNs } = require('../../common/composition')
|
|
4
4
|
|
|
5
5
|
const deleteFn = (executeDeleteCQN, executeUpdateCQN) => async (model, dbc, query, req) => {
|
|
6
6
|
const { user, locale, timestamp } = req
|
|
7
|
-
const isoTs =
|
|
7
|
+
const isoTs = normalizeTimestamp(timestamp)
|
|
8
8
|
|
|
9
9
|
let result
|
|
10
10
|
if (model && hasDeepDelete(model, query)) {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const { hasDeepInsert, getDeepInsertCQNs } = require('../../common/composition')
|
|
2
2
|
const { getFlatArray, processCQNs } = require('../utils/deep')
|
|
3
|
-
const
|
|
3
|
+
const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
|
|
4
4
|
|
|
5
5
|
const insert = executeInsertCQN => async (model, dbc, query, req) => {
|
|
6
6
|
const { user, locale, timestamp } = req
|
|
7
|
-
const isoTs =
|
|
7
|
+
const isoTs = normalizeTimestamp(timestamp)
|
|
8
8
|
|
|
9
9
|
if (model && hasDeepInsert(model, query)) {
|
|
10
10
|
const cqns = getFlatArray(getDeepInsertCQNs(model, query))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
|
|
2
2
|
const { deepCopyObject } = require('../../common/utils/copy')
|
|
3
3
|
const getError = require('../../common/error')
|
|
4
4
|
|
|
@@ -39,7 +39,7 @@ const countValue = countResults => {
|
|
|
39
39
|
|
|
40
40
|
const read = (executeSelectCQN, executeStreamCQN) => (model, dbc, query, req) => {
|
|
41
41
|
const { user, locale, timestamp } = req
|
|
42
|
-
const isoTs =
|
|
42
|
+
const isoTs = normalizeTimestamp(timestamp)
|
|
43
43
|
|
|
44
44
|
if (query._streaming) {
|
|
45
45
|
if (!query.SELECT || (query.SELECT && !query.SELECT.columns)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const
|
|
1
|
+
const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
|
|
2
2
|
|
|
3
3
|
const run = (insert, read, update, deleet, cqn, sql) => (model, dbc, query, req, values) => {
|
|
4
4
|
if (typeof query === 'string') {
|
|
@@ -22,7 +22,7 @@ const run = (insert, read, update, deleet, cqn, sql) => (model, dbc, query, req,
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const { user, locale, timestamp } = req
|
|
25
|
-
const isoTs =
|
|
25
|
+
const isoTs = normalizeTimestamp(timestamp)
|
|
26
26
|
|
|
27
27
|
return cqn(model, dbc, query, user, locale, isoTs)
|
|
28
28
|
}
|
|
@@ -2,7 +2,7 @@ const cds = require('../../cds')
|
|
|
2
2
|
|
|
3
3
|
const { hasDeepUpdate, getDeepUpdateCQNs, selectDeepUpdateData } = require('../../common/composition')
|
|
4
4
|
const { getFlatArray, processCQNs } = require('../utils/deep')
|
|
5
|
-
const
|
|
5
|
+
const normalizeTimestamp = require('../../common/utils/normalizeTimestamp')
|
|
6
6
|
|
|
7
7
|
const _includesCompositionTarget = (cqns, target) => {
|
|
8
8
|
return cqns.find(cqn => {
|
|
@@ -47,7 +47,7 @@ const _getFilteredCqns = (cqns, model) => {
|
|
|
47
47
|
|
|
48
48
|
const update = executeUpdateCQN => async (model, dbc, req) => {
|
|
49
49
|
const { query, user, locale, timestamp } = req
|
|
50
|
-
const isoTs =
|
|
50
|
+
const isoTs = normalizeTimestamp(timestamp)
|
|
51
51
|
|
|
52
52
|
if (model && hasDeepUpdate(model, query)) {
|
|
53
53
|
// REVISIT: _activeData gets set in case of draftActivate for performance, but this is a layer violation
|
|
@@ -35,12 +35,6 @@ class BaseBuilder {
|
|
|
35
35
|
this._quotingStyle = cds.env.sql.names || 'plain'
|
|
36
36
|
this._quoteElement = quotingStyles[this._quotingStyle]
|
|
37
37
|
this._validateQuotingStyle()
|
|
38
|
-
|
|
39
|
-
// NOTE: unofficial feature flag!
|
|
40
|
-
this._parameterizedNumbers =
|
|
41
|
-
'parameterized_numbers' in this._options
|
|
42
|
-
? this._options.parameterized_numbers
|
|
43
|
-
: cds.env && cds.env.features && cds.env.features.parameterized_numbers
|
|
44
38
|
}
|
|
45
39
|
|
|
46
40
|
getDefaultOptions() {
|
|
@@ -4,6 +4,7 @@ const BaseBuilder = require('./BaseBuilder')
|
|
|
4
4
|
const { flattenStructuredWhereHaving } = require('../../common/utils/structured')
|
|
5
5
|
|
|
6
6
|
const SQLITE_DATETIME_FUNCTIONS = new Set(['year', 'month', 'day', 'second', 'hour', 'minute'])
|
|
7
|
+
const HANA_DATETIME_FUNCTIONS = new Set(['year', 'month', 'dayofmonth', 'second', 'hour', 'minute'])
|
|
7
8
|
const OPERATORS = new Set(['=', '!=', '<>', '<', '>', '<=', '>='])
|
|
8
9
|
|
|
9
10
|
function _fillAfterDot(val) {
|
|
@@ -96,6 +97,11 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
96
97
|
if (_valButNoBuffer(op1) && comp === '=' && op2.ref) return true
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
_hasValToValComparision(op1, comp, op2) {
|
|
101
|
+
const _operations = ['=', '!=']
|
|
102
|
+
return typeof op1.val === 'number' && _operations.includes(comp) && typeof op2.val === 'number'
|
|
103
|
+
}
|
|
104
|
+
|
|
99
105
|
_expressionObjectsToSQL(objects) {
|
|
100
106
|
const length = objects.length
|
|
101
107
|
let i = 0
|
|
@@ -117,6 +123,14 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
117
123
|
this._expressionObjectsToSQL(flattenedStructExpression)
|
|
118
124
|
i += 3
|
|
119
125
|
continue
|
|
126
|
+
} else if (this._hasValToValComparision(objects[i], objects[i + 1], objects[i + 2])) {
|
|
127
|
+
// Avoid utilizing placeholders to represent numerical values being (not) equal to other numbers due to an issue related to HDB.
|
|
128
|
+
const _outputObj = this._outputObj
|
|
129
|
+
_outputObj.sql.push(objects[i].val)
|
|
130
|
+
_outputObj.sql.push(objects[i + 1])
|
|
131
|
+
_outputObj.sql.push(objects[i + 2].val)
|
|
132
|
+
i += 3
|
|
133
|
+
continue
|
|
120
134
|
}
|
|
121
135
|
|
|
122
136
|
this._expressionElementToSQL(objects[i])
|
|
@@ -147,17 +161,17 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
147
161
|
// sqlite requires leading 0 for numbers in datetime functions
|
|
148
162
|
const f = objects[i].func ? i : OPERATORS.has(objects[i + 1]) ? i + 2 : i - 2
|
|
149
163
|
const v = objects[i].val ? i : OPERATORS.has(objects[i + 1]) ? i + 2 : i - 2
|
|
150
|
-
if (
|
|
164
|
+
if (
|
|
165
|
+
objects[f] &&
|
|
166
|
+
cds.db &&
|
|
167
|
+
((SQLITE_DATETIME_FUNCTIONS.has(objects[f].func) && cds.db.kind === 'sqlite') ||
|
|
168
|
+
(HANA_DATETIME_FUNCTIONS.has(objects[f].func) && cds.db.kind === 'hana'))
|
|
169
|
+
) {
|
|
151
170
|
if (objects[v] && objects[v].val !== undefined && typeof objects[v].val === 'number') {
|
|
152
171
|
objects[v] = { val: `${objects[v].val < 10 ? 0 : ''}${objects[v].val}` }
|
|
153
172
|
if (objects[f].func === 'second') objects[v].val = _fillAfterDot(objects[v].val)
|
|
154
173
|
}
|
|
155
174
|
}
|
|
156
|
-
// odata indexof function returns the zero-based character position of the first occurrence
|
|
157
|
-
if (this._options._4odata && objects[i].func && objects[i].func === 'indexof') {
|
|
158
|
-
if (objects[i + 2] && objects[i + 2].val !== undefined) objects[i + 2].val++
|
|
159
|
-
else if (objects[i - 2] && objects[i - 2].val !== undefined) this._outputObj.sql[i - 2]++
|
|
160
|
-
}
|
|
161
175
|
return 0
|
|
162
176
|
}
|
|
163
177
|
|
|
@@ -343,12 +357,9 @@ class ExpressionBuilder extends BaseBuilder {
|
|
|
343
357
|
* @private
|
|
344
358
|
*/
|
|
345
359
|
_valOutputFromElement(element) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
this._outputObj.sql.push(this._options.placeholder)
|
|
350
|
-
this._outputObj.values.push(element.val)
|
|
351
|
-
}
|
|
360
|
+
const _outputObj = this._outputObj
|
|
361
|
+
_outputObj.sql.push(this._options.placeholder)
|
|
362
|
+
_outputObj.values.push(element.val)
|
|
352
363
|
}
|
|
353
364
|
|
|
354
365
|
_addToOutputObj({ sql, values }, addBrackets) {
|