@sap/cds 6.0.4 → 6.1.2

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.
Files changed (139) hide show
  1. package/CHANGELOG.md +180 -18
  2. package/apis/cds.d.ts +11 -7
  3. package/apis/log.d.ts +124 -0
  4. package/apis/ql.d.ts +72 -15
  5. package/apis/services.d.ts +13 -2
  6. package/bin/build/buildTaskHandler.js +5 -2
  7. package/bin/build/constants.js +4 -1
  8. package/bin/build/provider/buildTaskHandlerEdmx.js +11 -39
  9. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +14 -34
  10. package/bin/build/provider/buildTaskHandlerInternal.js +56 -4
  11. package/bin/build/provider/buildTaskProviderInternal.js +22 -14
  12. package/bin/build/provider/hana/index.js +12 -9
  13. package/bin/build/provider/java/index.js +18 -8
  14. package/bin/build/provider/mtx/index.js +7 -4
  15. package/bin/build/provider/mtx/resourcesTarBuilder.js +60 -35
  16. package/bin/build/provider/mtx-extension/index.js +57 -0
  17. package/bin/build/provider/mtx-sidecar/index.js +46 -18
  18. package/bin/build/provider/nodejs/index.js +34 -13
  19. package/bin/deploy/to-hana/cfUtil.js +7 -2
  20. package/bin/deploy/to-hana/hana.js +20 -25
  21. package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
  22. package/bin/serve.js +7 -4
  23. package/lib/compile/{index.js → cds-compile.js} +0 -0
  24. package/lib/compile/extend.js +15 -5
  25. package/lib/compile/minify.js +1 -15
  26. package/lib/compile/parse.js +1 -1
  27. package/lib/compile/resolve.js +2 -2
  28. package/lib/compile/to/srvinfo.js +6 -4
  29. package/lib/{deploy.js → dbs/cds-deploy.js} +9 -8
  30. package/lib/env/{index.js → cds-env.js} +1 -17
  31. package/lib/env/{requires.js → cds-requires.js} +24 -3
  32. package/lib/env/defaults.js +7 -1
  33. package/lib/env/schemas/cds-package.json +11 -0
  34. package/lib/env/schemas/cds-rc.json +614 -0
  35. package/lib/index.js +19 -16
  36. package/lib/log/{errors.js → cds-error.js} +1 -1
  37. package/lib/log/{index.js → cds-log.js} +0 -0
  38. package/lib/log/format/kibana.js +19 -1
  39. package/lib/ql/Query.js +9 -3
  40. package/lib/ql/SELECT.js +2 -2
  41. package/lib/ql/UPDATE.js +2 -2
  42. package/lib/ql/{index.js → cds-ql.js} +4 -10
  43. package/lib/req/context.js +49 -17
  44. package/lib/req/locale.js +5 -1
  45. package/lib/{serve → srv}/adapters.js +23 -19
  46. package/lib/{connect → srv}/bindings.js +0 -0
  47. package/lib/{connect/index.js → srv/cds-connect.js} +1 -1
  48. package/lib/{serve/index.js → srv/cds-serve.js} +0 -0
  49. package/lib/{serve → srv}/factory.js +1 -1
  50. package/lib/{serve/Service-api.js → srv/srv-api.js} +22 -6
  51. package/lib/{serve/Service-dispatch.js → srv/srv-dispatch.js} +13 -8
  52. package/lib/{serve/Service-handlers.js → srv/srv-handlers.js} +10 -0
  53. package/lib/{serve/Service-methods.js → srv/srv-methods.js} +10 -8
  54. package/lib/srv/srv-models.js +207 -0
  55. package/lib/{serve/Transaction.js → srv/srv-tx.js} +57 -40
  56. package/lib/utils/{tests.js → cds-test.js} +2 -2
  57. package/lib/utils/cds-utils.js +146 -0
  58. package/lib/utils/index.js +2 -145
  59. package/lib/utils/jest.js +43 -0
  60. package/lib/utils/resources/index.js +15 -25
  61. package/lib/utils/resources/tar.js +18 -41
  62. package/libx/_runtime/auth/index.js +14 -11
  63. package/libx/_runtime/cds-services/adapter/odata-v4/OData.js +7 -19
  64. package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +1 -4
  65. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/action.js +1 -4
  66. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/create.js +1 -4
  67. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/delete.js +1 -4
  68. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +2 -2
  69. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/metadata.js +6 -19
  70. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +3 -5
  71. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/request.js +1 -1
  72. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +1 -4
  73. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
  74. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
  75. package/libx/_runtime/cds-services/adapter/odata-v4/to.js +38 -4
  76. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +1 -3
  77. package/libx/_runtime/cds-services/services/utils/differ.js +4 -0
  78. package/libx/_runtime/cds-services/util/errors.js +1 -29
  79. package/libx/_runtime/common/i18n/messages.properties +2 -1
  80. package/libx/_runtime/common/perf/index.js +10 -15
  81. package/libx/_runtime/common/utils/binary.js +3 -4
  82. package/libx/_runtime/common/utils/cqn2cqn4sql.js +0 -1
  83. package/libx/_runtime/common/utils/entityFromCqn.js +8 -5
  84. package/libx/_runtime/common/utils/keys.js +14 -6
  85. package/libx/_runtime/common/utils/resolveView.js +1 -1
  86. package/libx/_runtime/common/utils/template.js +1 -1
  87. package/libx/_runtime/db/Service.js +2 -14
  88. package/libx/_runtime/db/expand/expandCQNToJoin.js +28 -25
  89. package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
  90. package/libx/_runtime/db/generic/input.js +8 -1
  91. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  92. package/libx/_runtime/db/sql-builder/SelectBuilder.js +37 -18
  93. package/libx/_runtime/extensibility/activate.js +47 -47
  94. package/libx/_runtime/extensibility/add.js +22 -13
  95. package/libx/_runtime/extensibility/addExtension.js +17 -13
  96. package/libx/_runtime/extensibility/defaults.js +25 -30
  97. package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
  98. package/libx/_runtime/extensibility/linter/allowlist_checker.js +373 -0
  99. package/libx/_runtime/extensibility/linter/annotations_checker.js +113 -0
  100. package/libx/_runtime/extensibility/linter/checker_base.js +20 -0
  101. package/libx/_runtime/extensibility/linter/namespace_checker.js +180 -0
  102. package/libx/_runtime/extensibility/linter.js +32 -0
  103. package/libx/_runtime/extensibility/push.js +77 -20
  104. package/libx/_runtime/extensibility/service.js +29 -12
  105. package/libx/_runtime/extensibility/token.js +57 -0
  106. package/libx/_runtime/extensibility/utils.js +8 -6
  107. package/libx/_runtime/extensibility/validation.js +6 -9
  108. package/libx/_runtime/fiori/generic/new.js +0 -11
  109. package/libx/_runtime/fiori/utils/where.js +1 -1
  110. package/libx/_runtime/hana/Service.js +0 -1
  111. package/libx/_runtime/hana/conversion.js +12 -1
  112. package/libx/_runtime/hana/customBuilder/CustomFunctionBuilder.js +4 -3
  113. package/libx/_runtime/hana/customBuilder/CustomSelectBuilder.js +5 -0
  114. package/libx/_runtime/hana/pool.js +6 -10
  115. package/libx/_runtime/hana/search2Contains.js +0 -5
  116. package/libx/_runtime/hana/search2cqn4sql.js +1 -0
  117. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  118. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
  119. package/libx/_runtime/messaging/outbox/utils.js +1 -1
  120. package/libx/_runtime/messaging/service.js +11 -6
  121. package/libx/_runtime/remote/utils/data.js +5 -0
  122. package/libx/_runtime/sqlite/Service.js +7 -6
  123. package/libx/_runtime/sqlite/execute.js +41 -28
  124. package/libx/odata/afterburner.js +79 -2
  125. package/libx/odata/cqn2odata.js +15 -9
  126. package/libx/odata/grammar.pegjs +157 -76
  127. package/libx/odata/index.js +9 -3
  128. package/libx/odata/parser.js +1 -1
  129. package/libx/odata/utils.js +39 -5
  130. package/libx/rest/RestAdapter.js +3 -7
  131. package/libx/rest/middleware/delete.js +4 -5
  132. package/libx/rest/middleware/parse.js +3 -2
  133. package/package.json +3 -3
  134. package/server.js +1 -1
  135. package/srv/extensibility-service.cds +6 -3
  136. package/srv/model-provider.cds +3 -1
  137. package/srv/model-provider.js +86 -106
  138. package/srv/mtx.js +7 -1
  139. package/libx/_runtime/cds-services/adapter/odata-v4/Dispatcher.js +0 -240
@@ -0,0 +1,180 @@
1
+ const Checker = require('./checker_base')
2
+
3
+ class NamespaceChecker extends Checker {
4
+ static async check(extensionCsn, fullCsn, compileDir, mtxConfig) {
5
+ let elementPrefixes = mtxConfig['element-prefix']
6
+ let namespaceBlocklist = mtxConfig['namespace-blocklist'] || mtxConfig['namespace-blacklist']
7
+ const warnings = []
8
+
9
+ if (elementPrefixes) {
10
+ if (!Array.isArray(elementPrefixes)) {
11
+ elementPrefixes = [elementPrefixes]
12
+ }
13
+
14
+ if (extensionCsn.extensions) {
15
+ // forall switches back to definitions if extensions are undefined
16
+ extensionCsn.forall(
17
+ () => true,
18
+ (element, name, parent) => {
19
+ element.name = name // TODO check if bug
20
+ this._checkElement(element, parent, elementPrefixes, compileDir, warnings)
21
+ },
22
+ extensionCsn.extensions
23
+ )
24
+ }
25
+
26
+ extensionCsn.forall(
27
+ element => {
28
+ return ['entity', 'function', 'action'].includes(element.kind)
29
+ },
30
+ entity => {
31
+ this._checkEntity(entity, extensionCsn, fullCsn, elementPrefixes, compileDir, warnings)
32
+ }
33
+ )
34
+ }
35
+
36
+ if (namespaceBlocklist) {
37
+ if (!Array.isArray(namespaceBlocklist)) {
38
+ namespaceBlocklist = [namespaceBlocklist]
39
+ }
40
+
41
+ extensionCsn.forall('service', service => {
42
+ this._checkNamespace(service, namespaceBlocklist, compileDir, warnings)
43
+ })
44
+
45
+ extensionCsn.forall(
46
+ element => {
47
+ return ['aspect', 'entity', 'type'].includes(element.kind)
48
+ },
49
+ entity => {
50
+ if (entity._unresolved) return // skip unresolved entities
51
+ this._checkNamespace(entity, namespaceBlocklist, compileDir, warnings)
52
+ }
53
+ )
54
+ }
55
+
56
+ return warnings
57
+ }
58
+
59
+ static _checkElement(element, parent, elementPrefixes, compileDir, warnings) {
60
+ if (elementPrefixes.length < 1) {
61
+ return
62
+ }
63
+
64
+ if (!parent) {
65
+ return
66
+ }
67
+
68
+ for (const elementPrefix of elementPrefixes) {
69
+ if (!parent.extend || element.name.startsWith(elementPrefix)) {
70
+ return
71
+ }
72
+ }
73
+
74
+ warnings.push(this._createPrefixWarning(element, compileDir, elementPrefixes))
75
+ }
76
+
77
+ static _checkEntity(element, reflectedCsn, reflectedFullCsn, elementPrefixes, compileDir, warnings) {
78
+ if (elementPrefixes.length < 1) {
79
+ return
80
+ }
81
+
82
+ if (!this._hasEnclosingEntity(reflectedCsn, element)) {
83
+ return
84
+ }
85
+
86
+ const parent = this._getEnclosingEntity(reflectedCsn, element)
87
+
88
+ // parent exists in extension
89
+ if (parent) {
90
+ return
91
+ }
92
+
93
+ // check full csn for parent - if it exists, continue
94
+ const parentFromFullCsn = this._getEnclosingEntity(reflectedFullCsn, element)
95
+ if (!parentFromFullCsn) {
96
+ return
97
+ }
98
+
99
+ // checks nested element - TODO determine real parent and split off parent name
100
+ const nestedElementName = this._getNestedEntityName(element) // ,parent
101
+
102
+ for (const elementPrefix of elementPrefixes) {
103
+ if (nestedElementName.startsWith(elementPrefix)) {
104
+ return
105
+ }
106
+ }
107
+
108
+ warnings.push(this._createPrefixWarning(element, compileDir, elementPrefixes))
109
+ }
110
+
111
+ static _hasEnclosingEntity(reflectedCsn, element) {
112
+ const plainEntityName = element.name.replace(reflectedCsn.namespace + '.', '')
113
+ const splitEntityName = plainEntityName.split('.')
114
+ if (splitEntityName.length > 1) {
115
+ return true
116
+ }
117
+ return false
118
+ }
119
+
120
+ static _getEnclosingEntity(reflectedCsn, element) {
121
+ const splitEntityName = element.name.split('.')
122
+ if (splitEntityName.length > 1) {
123
+ splitEntityName.pop()
124
+ return reflectedCsn.definitions[splitEntityName.join('.')]
125
+ }
126
+ return null
127
+ }
128
+
129
+ static _getNestedEntityName(element) {
130
+ const splitEntityName = element.name.split('.')
131
+ splitEntityName.shift()
132
+ return splitEntityName.join('.')
133
+ }
134
+
135
+ static _checkNamespace(element, namespaceBlacklist, compileDir, warnings) {
136
+ for (const namespace of namespaceBlacklist) {
137
+ if (element.name.startsWith(namespace)) {
138
+ warnings.push(this._createNamespaceWarning(element, compileDir, namespace))
139
+ }
140
+ }
141
+ }
142
+
143
+ static _createPrefixWarning(element, compileDir, prefixRule) {
144
+ const originFile = this._localizeFile(element.$location.file, compileDir)
145
+
146
+ return (
147
+ 'Element ' +
148
+ element.name +
149
+ ' from ' +
150
+ originFile +
151
+ ' (line:' +
152
+ element.$location.line +
153
+ ', col:' +
154
+ element.$location.col +
155
+ ')' +
156
+ ' does not adhere to prefix rule: ' +
157
+ prefixRule
158
+ )
159
+ }
160
+
161
+ static _createNamespaceWarning(element, compileDir, namespace) {
162
+ const originFile = this._localizeFile(element.$location.file, compileDir)
163
+
164
+ return (
165
+ 'Element ' +
166
+ element.name +
167
+ ' from ' +
168
+ originFile +
169
+ ' (line:' +
170
+ element.$location.line +
171
+ ', col:' +
172
+ element.$location.col +
173
+ ')' +
174
+ ' uses a forbidden namespace: ' +
175
+ namespace
176
+ )
177
+ }
178
+ }
179
+
180
+ module.exports = NamespaceChecker
@@ -0,0 +1,32 @@
1
+ const cds = require('../cds')
2
+
3
+ const NamespaceChecker = require('./linter/namespace_checker')
4
+ const AnnotationsChecker = require('./linter/annotations_checker')
5
+ const AllowlistChecker = require('./linter/allowlist_checker')
6
+
7
+ const LINTER_OPTIONS = ['element-prefix', 'extension-allowlist', 'namespace-blocklist']
8
+ const LEGACY_OPTIONS = ['entity-whitelist', 'service-whitelist', 'namespace-blacklist']
9
+
10
+ const linter = async (extCsn, fullCsn, extensionFilenames, req) => {
11
+ const conf = cds.env.requires['cds.xt.ExtensibilityService'] || cds.env.mtx
12
+ const compat = cds.env.mtx
13
+ const linter_options = {}
14
+ let x
15
+ for (let p of LINTER_OPTIONS) if ((x = conf[p] || compat[p])) linter_options[p] = x // eslint-disable-line no-cond-assign
16
+ for (let p of LEGACY_OPTIONS) if ((x = compat[p])) linter_options[p] = x // eslint-disable-line no-cond-assign
17
+ if (!Object.keys(linter_options).length) return
18
+
19
+ const reflectedCsn = cds.reflect(extCsn)
20
+ const compileBaseDir = global.cds.root
21
+ const warnings = await Promise.all([
22
+ NamespaceChecker.check(reflectedCsn, fullCsn, compileBaseDir, linter_options),
23
+ AnnotationsChecker.check(reflectedCsn, extensionFilenames, compileBaseDir, linter_options),
24
+ AllowlistChecker.check(reflectedCsn, fullCsn, extensionFilenames, compileBaseDir, linter_options)
25
+ ])
26
+ const linterWarnings = [].concat.apply([], warnings) // REVISIT: What are we doing here?
27
+ if (linterWarnings.length > 0) {
28
+ req.reject(422, linterWarnings[0]) // REVISIT: Why are we returning the first warning only?
29
+ }
30
+ }
31
+
32
+ module.exports = linter
@@ -3,29 +3,29 @@ const path = require('path')
3
3
  const cds = require('../cds')
4
4
 
5
5
  const activate = require('./activate')
6
- const { validateExtension } = require('./validation')
7
- const { collectFiles, getCompilerError, exists } = require('./utils')
6
+ const { collectFiles, getCompilerError } = require('./utils')
8
7
  const { packTarArchive, unpackTarArchive } = require('../../../lib/utils/resources')
8
+ const linter = require('./linter')
9
9
 
10
10
  const TEMP_DIR = fs.realpathSync(require('os').tmpdir())
11
+ const LOG = cds.log('mtx')
11
12
 
12
13
  const _compileProject = async function (extension, req) {
13
- let csn, root
14
+ let csn, root, files
14
15
  try {
15
16
  root = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}extension-`)
16
17
  await unpackTarArchive(extension, root)
17
- csn = await cds.compile(collectFiles(root, ['.cds', '.json']), { flavor: 'parsed' })
18
+ files = collectFiles(root, ['.cds', '.csn']) // REVISIT: don't we have exactly one ext.csn file for all extensions?
19
+ csn = await cds.compile(files, { flavor: 'parsed' })
18
20
  if (csn.requires) delete csn.requires
19
21
  } catch (err) {
20
22
  if (err.messages) req.reject(400, getCompilerError(err.messages))
21
23
  else throw err
22
24
  } finally {
23
- if (await exists(root)) {
24
- await (fs.promises.rm || fs.promises.rmdir)(root, { recursive: true, force: true })
25
- }
25
+ fs.promises.rm(root, { recursive: true, force: true }).catch(() => {})
26
26
  }
27
27
 
28
- return csn
28
+ return { csn, files }
29
29
  }
30
30
 
31
31
  const base = async function (req) {
@@ -38,24 +38,81 @@ const base = async function (req) {
38
38
  return packTarArchive([...cdsFiles, ...csvFiles, ...i18nFiles], cds.root)
39
39
  }
40
40
 
41
+ // const _copyFile = async function (file, dir) {
42
+ // const destination = path.join(dir, path.relative(cds.root, file))
43
+ // const dirname = path.dirname(destination)
44
+ // if (!(await exists(dirname))) await fs.promises.mkdir(dirname, { recursive: true })
45
+ // await fs.promises.copyFile(file, destination)
46
+ // }
47
+
48
+ const pull = async function (req) {
49
+ LOG.info(`pulling latest model for tenant '${req.tenant}'`)
50
+ const { 'cds.xt.ModelProviderService': mps } = cds.services
51
+ const csn = await mps.getCsn({
52
+ tenant: req.tenant,
53
+ toggles: Object.keys(cds.context.features || {}), // with all enabled feature extensions
54
+ base: true, // without any custom extensions
55
+ flavor: 'xtended'
56
+ })
57
+ // const csvObj = await cds.deploy.resources()
58
+ // const csvFiles = Object.keys(csvObj).filter(f => f.startsWith(cds.root) && !f.includes('node_modules'))
59
+ // const i18nFiles = collectFiles(cds.root, ['.properties'])
60
+
61
+ req._.res?.set('content-type', 'application/octet-stream; charset=binary')
62
+
63
+ let temp, tgz
64
+ try {
65
+ temp = await fs.promises.mkdtemp(`${TEMP_DIR}${path.sep}extension-`)
66
+ await fs.promises.writeFile(path.join(temp, 'index.csn'), cds.compile.to.json(csn))
67
+ // for (const file of csvFiles) await _copyFile(file, temp)
68
+ // for (const file of i18nFiles) await _copyFile(file, temp)
69
+ tgz = await packTarArchive(temp)
70
+ } finally {
71
+ fs.promises.rm(temp, { recursive: true, force: true }).catch(() => {})
72
+ }
73
+
74
+ return tgz
75
+ }
76
+
41
77
  const push = async function (req) {
42
- let { extension } = req.data
43
- if (!extension || !extension.data) req.reject(400, 'Missing extension')
44
- const csn = await _compileProject(extension.data, req)
45
- if (!csn) req.reject(400, 'Missing or bad extension')
78
+ let { extension, tag } = req.data
79
+ if (!extension) req.reject(400, 'Missing extension')
80
+ const sources = typeof extension === 'string' ? Buffer.from(extension, 'base64') : extension
81
+ const { csn: extCsn, files } = await _compileProject(sources, req)
82
+ if (!extCsn) req.reject(400, 'Missing or bad extension')
83
+ if (!tag) tag = null
46
84
  const tenant = req.tenant
47
- await validateExtension(csn, tenant, req)
85
+ if (tenant) cds.context = { tenant }
86
+
87
+ // remove current extension with tag
88
+ if (tag) {
89
+ await DELETE.from('cds.xt.Extensions').where({ tag })
90
+ }
91
+
92
+ LOG.info(`validating extension '${tag}' ...`)
93
+ // validation
94
+ const { 'cds.xt.ModelProviderService': mps } = cds.services
95
+ // REVISIT: Isn't that also done during activate?
96
+ const csn = await mps.getCsn(tenant, Object.keys(cds.context.features || {}))
97
+ try {
98
+ cds.extend(csn).with(extCsn)
99
+ } catch (err) {
100
+ return req.reject(400, getCompilerError(err.messages))
101
+ }
102
+ await linter(extCsn, csn, files, req)
48
103
 
104
+ // insert and activate extension
49
105
  const ID = cds.utils.uuid()
50
- await cds.tx({ tenant }, async tx => {
51
- await tx.run(
52
- INSERT.into('cds.xt.Extensions').entries([
53
- { ID, csn: JSON.stringify(csn), sources: extension.data, activated: 'database' }
54
- ])
55
- )
106
+ await INSERT.into('cds.xt.Extensions').entries({
107
+ ID,
108
+ csn: JSON.stringify(extCsn),
109
+ sources,
110
+ activated: 'database',
111
+ tag
56
112
  })
57
113
 
114
+ LOG.info(`activating extension '${tag}' ...`)
58
115
  await activate(ID, null, tenant)
59
116
  }
60
117
 
61
- module.exports = { base, push }
118
+ module.exports = { base, push, pull }
@@ -2,20 +2,37 @@ const cds = require('../cds')
2
2
 
3
3
  const addExtension = require('./addExtension')
4
4
  const { add, promote } = require('./add')
5
- const { base, push } = require('./push')
5
+ const { base, push, pull } = require('./push')
6
+ const { token } = require('./token')
6
7
  const { transformExtendedFieldsCREATE, transformExtendedFieldsUPDATE } = require('./handler/transformWRITE')
7
8
  const { transformExtendedFieldsREAD } = require('./handler/transformREAD')
8
9
  const { transformExtendedFieldsRESULT } = require('./handler/transformRESULT')
9
10
 
10
- module.exports = async function () {
11
- this.on('addExtension', addExtension)
12
- this.on('add', add)
13
- this.on('promote', promote)
14
- this.on('base', base)
15
- this.on('push', push)
16
- cds.db
17
- .before('CREATE', transformExtendedFieldsCREATE)
18
- .before('UPDATE', transformExtendedFieldsUPDATE)
19
- .before('READ', transformExtendedFieldsREAD)
20
- .after('READ', transformExtendedFieldsRESULT)
11
+ module.exports = class ExtensibilityService extends cds.ApplicationService {
12
+ init() {
13
+ this.on('addExtension', addExtension)
14
+ this.on('add', add)
15
+ this.on('promote', promote)
16
+ this.on('base', base)
17
+ this.on('push', push)
18
+ this.on('pull', pull)
19
+
20
+ cds.on('served', () => cds.app.get('/-/cds/login/token', token))
21
+
22
+ const { 'cds.xt.ModelProviderService': mps } = cds.services
23
+ // REVISIT: mps._in_sidecar -> revisit options
24
+ if (!mps?._in_sidecar)
25
+ cds.db
26
+ .before('CREATE', transformExtendedFieldsCREATE)
27
+ .before('UPDATE', transformExtendedFieldsUPDATE)
28
+ .before('READ', transformExtendedFieldsREAD)
29
+ .after('READ', transformExtendedFieldsRESULT)
30
+
31
+ return super.init()
32
+ }
33
+
34
+ // REVISIT: Do we want to keep this?
35
+ get isExtensible() {
36
+ return false
37
+ }
21
38
  }
@@ -0,0 +1,57 @@
1
+ const { URL } = require('url')
2
+ const cds = require('../../../lib')
3
+ const LOG = cds.log()
4
+
5
+ module.exports = {
6
+ async token(request, response) {
7
+ if (request.method === 'HEAD') {
8
+ response.status(204).send()
9
+ return
10
+ }
11
+
12
+ const { passcode, refresh_token, subdomain, clientid, clientsecret } = request.query
13
+ const { credentials } = cds.env.requires.auth
14
+ if (!credentials) {
15
+ cds.error(
16
+ 'No auth credentials defined. The application is likely not bound to an authentication service instance.'
17
+ )
18
+ }
19
+
20
+ const parsedUrl = new URL(credentials.url)
21
+ parsedUrl.hostname = subdomain + '.' + parsedUrl.hostname.split('.').slice(1).join('.')
22
+
23
+ LOG.info(`Get auth token using URL ${parsedUrl}`)
24
+
25
+ if (clientid) {
26
+ LOG.info(`Using clientid/clientsecret from API call with clientid ${clientid}`)
27
+ }
28
+
29
+ const username = clientid ? clientid : credentials.clientid
30
+ const password = clientid ? clientsecret : credentials.clientsecret
31
+ const { xsappname } = cds.env.requires.auth?.credentials ?? cds.env.requires.uaa?.credentials ?? {}
32
+ const path =
33
+ (refresh_token
34
+ ? `oauth/token?grant_type=refresh_token&refresh_token=${refresh_token}`
35
+ : `oauth/token?grant_type=password&passcode=${encodeURIComponent(passcode)}`) +
36
+ `&scope=${encodeURIComponent(xsappname + '.cds.ExtensionDeveloper')}`
37
+
38
+ try {
39
+ const { data } = await require('axios').post(
40
+ parsedUrl + path,
41
+ { 'Content-Type': 'application/json' },
42
+ { auth: { username, password } }
43
+ )
44
+ response.send(data)
45
+ } catch (error) {
46
+ const rootCause = error.response?.data ? JSON.stringify(error.response?.data) : error.message
47
+ error.message = `Authentication failed with root cause '${rootCause}'. Passcode URL: https://${parsedUrl.hostname}/passcode`
48
+ const {
49
+ constructor: { name },
50
+ message
51
+ } = error
52
+ const status = name in { JwtRequestError: 1, IncompleteJwtResponseError: 1 } ? 401 : error.response.status ?? 500
53
+ LOG.error(message)
54
+ response.status(status).send({ message, status })
55
+ }
56
+ }
57
+ }
@@ -8,7 +8,7 @@ const EXT_BACK_PACK = 'extensions__'
8
8
 
9
9
  const getTargetRead = req => {
10
10
  let name = ''
11
- if (req.query.SELECT.from.join) {
11
+ if (req.query.SELECT.from.join && req.query.SELECT.from.args) {
12
12
  // join
13
13
  name = req.query.SELECT.from.args.find(arg => arg.ref && arg.ref[0] !== 'DRAFT.DraftAdministativeData').ref[0]
14
14
  } else if (req.target.name.SET) {
@@ -63,15 +63,17 @@ const hasExtendedEntity = (req, model) => {
63
63
  return true
64
64
  }
65
65
 
66
- if (req.query.SELECT.from.join) {
66
+ if (req.query.SELECT.from.join && req.query.SELECT.from.args) {
67
67
  return _hasExtendedEntityArgs(req.query.SELECT.from.args, model)
68
68
  }
69
69
 
70
- if (req.target.name.SET) {
71
- return isExtendedEntity(req.target.name.SET.args[0]._target.name, model)
72
- }
70
+ if (req.target) {
71
+ if (req.target.name.SET) {
72
+ return isExtendedEntity(req.target.name.SET.args[0]._target.name, model)
73
+ }
73
74
 
74
- return isExtendedEntity(req.target.name, model)
75
+ return isExtendedEntity(req.target.name, model)
76
+ }
75
77
  }
76
78
 
77
79
  const getExtendedFields = (entityName, model) => {
@@ -2,12 +2,12 @@ const cds = require('../cds')
2
2
 
3
3
  const { getCompilerError } = require('./utils')
4
4
 
5
- const validateCsn = (csn, req) => {
5
+ const validateCsn = (csn, appCsn, req) => {
6
6
  if (!csn) req.reject(400, 'Missing extension')
7
7
  if (!csn.extensions) return
8
8
 
9
9
  csn.extensions.forEach(extension => {
10
- if (!extension.extend || !cds.model.definitions[extension.extend]) {
10
+ if (!extension.extend || !appCsn.definitions[extension.extend]) {
11
11
  req.reject(400, 'Invalid extension. Parameter "extend" missing or malformed')
12
12
  }
13
13
 
@@ -17,7 +17,7 @@ const validateCsn = (csn, req) => {
17
17
  })
18
18
  }
19
19
 
20
- const validateExtensionFields = (csn, req) => {
20
+ const validateExtensionFields = (csn, appCsn, req) => {
21
21
  if (!csn.extensions) return
22
22
 
23
23
  csn.extensions.forEach(extension => {
@@ -27,7 +27,7 @@ const validateExtensionFields = (csn, req) => {
27
27
  req.reject(400, `Invalid extension. Bad element name "${name}"`)
28
28
  }
29
29
 
30
- if (Object.keys(cds.model.definitions[extension.extend].elements).includes(name)) {
30
+ if (Object.keys(appCsn.definitions[extension.extend].elements).includes(name)) {
31
31
  req.reject(400, `Invalid extension. Element "${name}" already exists`)
32
32
  }
33
33
  })
@@ -35,12 +35,9 @@ const validateExtensionFields = (csn, req) => {
35
35
  })
36
36
  }
37
37
 
38
- const validateExtension = async (ext, tenant, req) => {
38
+ const validateExtension = (ext, csn, req) => {
39
39
  try {
40
- const { 'cds.xt.ModelProviderService': mps } = cds.services
41
- const csn = await mps.getCsn(tenant, ['*'])
42
- const extCsn = cds.compile.to.json(ext)
43
- await cds.compile.to.csn({ 'base.csn': JSON.stringify(csn), 'ext.csn': extCsn })
40
+ cds.extend(csn).with(ext)
44
41
  } catch (err) {
45
42
  req.reject(400, getCompilerError(err.messages))
46
43
  }
@@ -56,17 +56,6 @@ const _handler = async function (req, next) {
56
56
  return onDraftActivate(req, next)
57
57
  }
58
58
 
59
- // fill default values
60
- const elements = req.target.elements
61
- for (const column in elements) {
62
- const col = elements[column]
63
- if (col.default !== undefined && !(column in DRAFT_COLUMNS_MAP)) {
64
- if ('val' in col.default) req.data[col.name] = col.default.val
65
- else if ('ref' in col.default) req.data[col.name] = col.default.ref[0]
66
- else req.data[col.name] = col.default
67
- }
68
- }
69
-
70
59
  const navigationToMany = isNavigationToMany(req)
71
60
 
72
61
  const adminDataCQN = navigationToMany
@@ -160,7 +160,7 @@ const isActiveEntityRequested = where => {
160
160
 
161
161
  while (where[i]) {
162
162
  if (where[i].xpr) {
163
- const isRequested = isActiveEntityRequested(where.xpr)
163
+ const isRequested = isActiveEntityRequested(where[i].xpr)
164
164
  if (isRequested) return true
165
165
  }
166
166
  if (
@@ -91,7 +91,6 @@ class HanaDatabase extends DatabaseService {
91
91
  }
92
92
 
93
93
  _registerBeforeHandlers() {
94
- this._ensureModel && this.before('*', this._ensureModel)
95
94
  this.before(['CREATE', 'UPDATE'], '*', this._input) // > has to run before rewrite
96
95
  this.before('READ', '*', search) // > has to run before rewrite
97
96
  this.before(['CREATE', 'READ', 'UPDATE', 'DELETE'], '*', this._rewrite)
@@ -1,5 +1,8 @@
1
1
  const cds = require('../cds')
2
2
 
3
+ const driver = require('./driver')
4
+ const isHdb = driver?.name === 'hdb'
5
+
3
6
  const convertToBoolean = boolean => {
4
7
  if (boolean === null) {
5
8
  return null
@@ -35,7 +38,15 @@ const convertToISONoMillis = element => {
35
38
 
36
39
  const convertToString = element => {
37
40
  if (element) {
38
- return element instanceof Buffer ? Buffer.from(element, 'base64').toString() : element
41
+ if (element instanceof Buffer) {
42
+ if (isHdb && driver.iconv) {
43
+ return driver.iconv.decode(element, 'cesu8')
44
+ }
45
+
46
+ return Buffer.from(element, 'base64').toString()
47
+ }
48
+
49
+ return element
39
50
  }
40
51
 
41
52
  return null
@@ -21,12 +21,13 @@ class CustomFunctionBuilder extends FunctionBuilder {
21
21
 
22
22
  _handleContains(args) {
23
23
  // fuzzy search has three arguments, must not be converted to like expressions
24
- if (args.length > 2 || args._$search) {
24
+ if (args.length > 2 || this._options.$searchUsingContains) {
25
25
  this._outputObj.sql.push('CONTAINS')
26
26
  this._addFunctionArgs(args, true)
27
- } else {
28
- super._handleContains(args)
27
+ return
29
28
  }
29
+
30
+ super._handleContains(args)
30
31
  }
31
32
  }
32
33
 
@@ -28,6 +28,11 @@ class CustomSelectBuilder extends SelectBuilder {
28
28
  return SelectBuilder
29
29
  }
30
30
 
31
+ getDefaultOptions() {
32
+ const options = { $searchUsingContains: !!this._obj.SELECT._$searchUsingContains }
33
+ return { ...super.getDefaultOptions(), ...options }
34
+ }
35
+
31
36
  _val(obj) {
32
37
  if (typeof obj.val === 'boolean') return { sql: obj.val ? 'true' : 'false', values: [] }
33
38
  return super._val(obj)
@@ -10,13 +10,9 @@ const getError = require('../common/error')
10
10
  function multiTenantInstanceManager(config = cds.env.requires.db) {
11
11
  const { credentials } = config
12
12
 
13
- if (
14
- !credentials ||
15
- typeof credentials !== 'object' ||
16
- !(credentials.get_managed_instance_url || credentials.sm_url)
17
- ) {
18
- throw Object.assign(new Error('No or malformed db credentials'), { credentials: credentials })
19
- }
13
+ if (!credentials) throw Object.assign(new Error('No database credentials provided'))
14
+ else if (typeof credentials !== 'object' || !(credentials.get_managed_instance_url || credentials.sm_url))
15
+ throw Object.assign(new Error('Malformed database credentials provided'))
20
16
 
21
17
  // new instance manager
22
18
  return new Promise((resolve, reject) => {
@@ -53,9 +49,9 @@ function multiTenantInstanceManager(config = cds.env.requires.db) {
53
49
  function singleTenantInstanceManager(config = cds.env.requires.db) {
54
50
  const { credentials } = config
55
51
 
56
- if (!credentials || typeof credentials !== 'object' || !credentials.host) {
57
- throw Object.assign(new Error('No or malformed db credentials'), { credentials })
58
- }
52
+ if (!credentials) throw Object.assign(new Error('No database credentials provided'))
53
+ else if (typeof credentials !== 'object' || !credentials.host)
54
+ throw Object.assign(new Error('Malformed database credentials provided'))
59
55
 
60
56
  // mock instance manager
61
57
  return {
@@ -51,11 +51,6 @@ const search2Contains = (cqnSearchPhrase, columns) => {
51
51
 
52
52
  const expressionArgs = [{ list: columns }, { val: searchString }]
53
53
 
54
- // REVISIT: Mark the expression args with a `_$search` flag, as the `CONTAINS`
55
- // predicate is not fully supported in the CustomFunctionBuilder class.
56
- // The `_$search` property is enumerable: false (default), writable: false (default)
57
- Object.defineProperty(expressionArgs, '_$search', { value: true })
58
-
59
54
  const expression = {
60
55
  func: 'contains',
61
56
  args: expressionArgs