@sap/cds 6.6.2 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +59 -2
- package/README.md +1 -1
- package/apis/connect.d.ts +11 -4
- package/apis/core.d.ts +1 -1
- package/apis/csn.d.ts +1 -0
- package/apis/internal/inference.d.ts +15 -2
- package/apis/log.d.ts +10 -0
- package/apis/serve.d.ts +4 -9
- package/apis/services.d.ts +86 -19
- package/bin/build/buildTaskEngine.js +16 -42
- package/bin/build/constants.js +4 -2
- package/bin/build/provider/buildTaskProviderInternal.js +117 -85
- package/bin/build/provider/hana/index.js +6 -1
- package/bin/build/provider/mtx-extension/index.js +74 -34
- package/bin/build/provider/mtx-sidecar/index.js +3 -3
- package/bin/build/provider/nodejs/index.js +2 -2
- package/bin/build/util.js +63 -14
- package/bin/cds-serve.js +6 -0
- package/bin/cds.js +20 -4
- package/bin/deploy/to-hana/cfUtil.js +15 -1
- package/bin/mtx/in-cds.js +2 -9
- package/bin/plugins.js +31 -0
- package/bin/serve.js +12 -12
- package/lib/compile/etc/_localized.js +1 -1
- package/lib/compile/for/lean_drafts.js +22 -6
- package/lib/compile/for/nodejs.js +4 -1
- package/lib/compile/load.js +4 -2
- package/lib/core/index.js +35 -15
- package/lib/dbs/cds-deploy.js +129 -133
- package/lib/env/cds-env.js +25 -17
- package/lib/env/cds-requires.js +10 -40
- package/lib/env/compat.js +12 -0
- package/lib/env/defaults.js +17 -9
- package/lib/env/plugins.js +29 -0
- package/lib/env/schemas/cds-rc.json +14 -0
- package/lib/index.js +3 -0
- package/lib/log/cds-log.js +7 -4
- package/lib/ql/CREATE.js +1 -1
- package/lib/ql/DELETE.js +1 -1
- package/lib/ql/DROP.js +3 -3
- package/lib/ql/INSERT.js +1 -1
- package/lib/ql/Query.js +14 -6
- package/lib/ql/SELECT.js +8 -2
- package/lib/ql/UPDATE.js +1 -1
- package/lib/ql/Whereable.js +1 -1
- package/lib/ql/cds-ql.js +1 -9
- package/lib/req/cds-context.js +1 -4
- package/lib/req/request.js +63 -2
- package/lib/req/response.js +3 -2
- package/lib/srv/bindings.js +69 -71
- package/lib/srv/cds-connect.js +4 -1
- package/lib/srv/cds-serve.js +4 -0
- package/lib/srv/middlewares/index.js +37 -6
- package/lib/srv/protocols/_legacy.js +1 -1
- package/lib/srv/protocols/index.js +1 -1
- package/lib/srv/srv-api.js +4 -6
- package/lib/srv/srv-dispatch.js +4 -3
- package/lib/srv/srv-handlers.js +1 -1
- package/lib/srv/srv-methods.js +8 -2
- package/lib/utils/cds-test.js +4 -1
- package/libx/_runtime/audit/Service.js +8 -9
- package/libx/_runtime/audit/generic/personal/index.js +1 -1
- package/libx/_runtime/audit/generic/personal/utils.js +1 -1
- package/libx/_runtime/audit/utils/v2.js +17 -20
- package/libx/_runtime/cds-services/adapter/odata-v4/ODataRequest.js +2 -0
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +11 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/update.js +0 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/odata-to-cqn/readToCQN.js +3 -3
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/data.js +1 -1
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/metaInfo.js +4 -4
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/oDataConfiguration.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/utils/result.js +1 -1
- package/libx/_runtime/cds-services/services/Service.js +1 -1
- package/libx/_runtime/cds-services/util/assert.js +41 -65
- package/libx/_runtime/common/code-ext/WorkerPool.js +90 -0
- package/libx/_runtime/common/code-ext/WorkerReq.js +0 -4
- package/libx/_runtime/common/code-ext/execute.js +28 -18
- package/libx/_runtime/common/code-ext/handlers.js +5 -4
- package/libx/_runtime/common/code-ext/worker.js +45 -3
- package/libx/_runtime/common/code-ext/workerQueryExecutor.js +8 -7
- package/libx/_runtime/common/composition/delete.js +1 -1
- package/libx/_runtime/common/composition/update.js +3 -5
- package/libx/_runtime/common/generic/auth/expand.js +1 -1
- package/libx/_runtime/common/generic/auth/readOnly.js +5 -4
- package/libx/_runtime/common/generic/auth/restrict.js +7 -2
- package/libx/_runtime/common/generic/crud.js +12 -1
- package/libx/_runtime/common/generic/etag.js +11 -3
- package/libx/_runtime/common/generic/input.js +8 -6
- package/libx/_runtime/common/generic/paging.js +25 -8
- package/libx/_runtime/common/generic/put.js +1 -1
- package/libx/_runtime/common/generic/sorting.js +0 -1
- package/libx/_runtime/common/i18n/messages.properties +1 -0
- package/libx/_runtime/common/utils/cqn.js +5 -1
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +2 -2
- package/libx/_runtime/common/utils/resolveView.js +14 -10
- package/libx/_runtime/common/utils/rewriteAsterisks.js +2 -3
- package/libx/_runtime/common/utils/templateProcessor.js +15 -17
- package/libx/_runtime/common/utils/templateProcessorPathSerializer.js +18 -6
- package/libx/_runtime/db/Service.js +1 -0
- package/libx/_runtime/db/data-conversion/post-processing.js +0 -18
- package/libx/_runtime/db/expand/expand-v2.js +2 -2
- package/libx/_runtime/db/expand/rawToExpanded.js +6 -6
- package/libx/_runtime/db/generic/integrity.js +1 -1
- package/libx/_runtime/db/utils/columns.js +5 -5
- package/libx/_runtime/fiori/generic/activate.js +3 -3
- package/libx/_runtime/fiori/generic/edit.js +1 -1
- package/libx/_runtime/fiori/generic/new.js +4 -0
- package/libx/_runtime/fiori/lean-draft.js +138 -46
- package/libx/_runtime/hana/execute.js +3 -1
- package/libx/_runtime/hana/pool.js +10 -2
- package/libx/_runtime/messaging/common-utils/AMQPClient.js +6 -1
- package/libx/_runtime/messaging/enterprise-messaging.js +1 -0
- package/libx/_runtime/remote/Service.js +16 -13
- package/libx/_runtime/remote/utils/client.js +6 -1
- package/libx/_runtime/sqlite/Service.js +5 -59
- package/libx/_runtime/sqlite/convertDraftAdminPathExpression.js +1 -0
- package/libx/_runtime/sqlite/customBuilder/CustomUpsertBuilder.js +2 -2
- package/libx/_runtime/sqlite/execute.js +3 -1
- package/libx/_runtime/types/api.js +12 -3
- package/libx/odata/afterburner.js +36 -0
- package/libx/odata/cqn2odata.js +1 -1
- package/libx/odata/grammar.pegjs +5 -3
- package/libx/odata/parser.js +1 -1
- package/libx/odata/utils.js +1 -1
- package/libx/rest/RestAdapter.js +1 -1
- package/libx/rest/RestRequest.js +1 -0
- package/package.json +5 -2
- package/libx/_runtime/common/code-ext/workerQuery.js +0 -45
- package/libx/_runtime/common/constants/limit.js +0 -12
- package/libx/_runtime/common/utils/page.js +0 -39
|
@@ -13,7 +13,7 @@ const cds = require('../../../lib');
|
|
|
13
13
|
const LOG = cds.log ? cds.log('deploy') : console;
|
|
14
14
|
const DEBUG = cds.debug('deploy');
|
|
15
15
|
|
|
16
|
-
const { bold } = require('../../utils/term');
|
|
16
|
+
const { bold, warn } = require('../../utils/term');
|
|
17
17
|
|
|
18
18
|
const CF_COMMAND = 'cf';
|
|
19
19
|
|
|
@@ -25,6 +25,8 @@ const OPERATION_STATE_IN_PROGRESS = 'in progress';
|
|
|
25
25
|
const OPERATION_STATE_FAILED = 'failed';
|
|
26
26
|
const OPERATION_STATE_SUCCEEDED = 'succeeded';
|
|
27
27
|
|
|
28
|
+
const CF_CLIENT_MINIMUM_VERSION = 8;
|
|
29
|
+
|
|
28
30
|
|
|
29
31
|
class CfUtil {
|
|
30
32
|
|
|
@@ -135,10 +137,22 @@ class CfUtil {
|
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
async getCfTarget() {
|
|
140
|
+
await this.checkCliVersion();
|
|
138
141
|
await this._cfRun('oauth-token'); // check if token is valid or expired / missing
|
|
139
142
|
return await this.getCfTargetFromConfigFile() || await this.getCfTargetFromCli();
|
|
140
143
|
}
|
|
141
144
|
|
|
145
|
+
async checkCliVersion() {
|
|
146
|
+
const result = await this._cfRun('-v');
|
|
147
|
+
const version = result?.stdout?.match(/version.*(\d+\.\d+\.\d+)/i)
|
|
148
|
+
if (parseInt(version?.[1]) < CF_CLIENT_MINIMUM_VERSION) {
|
|
149
|
+
console.log(warn(`
|
|
150
|
+
[Warning] You are using Cloud Foundry client version ${version[1]}. We recommend version ${CF_CLIENT_MINIMUM_VERSION} or higher.
|
|
151
|
+
Deployment will stop in the near future for Cloud Foundry client versions < ${CF_CLIENT_MINIMUM_VERSION}.
|
|
152
|
+
`));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
142
156
|
async getCfSpaceInfo() {
|
|
143
157
|
if (!this.spaceInfo) {
|
|
144
158
|
LOG.debug('getting space info');
|
package/bin/mtx/in-cds.js
CHANGED
|
@@ -1,14 +1,6 @@
|
|
|
1
1
|
const cds = require('../build/cds')
|
|
2
|
-
const { _oldMtx } = cds.utils
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
if (_oldMtx()) return false
|
|
6
|
-
try { return !!require.resolve('@sap/cds-mtxs/srv/deployment-service') }
|
|
7
|
-
catch {/* ignored */}
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
if (!cds.requires.multitenancy || _is_streamlined_mtx()) module.exports = undefined
|
|
11
|
-
else try {
|
|
3
|
+
if (cds.requires.multitenancy && cds.utils._oldMtx()) try {
|
|
12
4
|
// eslint-disable-next-line cds/no-missing-dependencies
|
|
13
5
|
const mtx = module.exports = require ('@sap/cds-mtx')()
|
|
14
6
|
mtx.inject (cds)
|
|
@@ -17,6 +9,7 @@ else try {
|
|
|
17
9
|
if (e.code === 'MODULE_NOT_FOUND') throw new Error('Error serving MTX APIs: @sap/cds-mtx is not installed')
|
|
18
10
|
else throw e
|
|
19
11
|
}
|
|
12
|
+
else module.exports = undefined
|
|
20
13
|
|
|
21
14
|
/*
|
|
22
15
|
cds.requires.multitenancy -- use this to check whether mt is enabled (old or new)
|
package/bin/plugins.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const cds = require('../lib')
|
|
2
|
+
const DEBUG = cds.debug('plugins')
|
|
3
|
+
|
|
4
|
+
module.exports = async function load_plugins (log = console.log) {
|
|
5
|
+
|
|
6
|
+
if (DEBUG) console.time('[cds] - loaded plugins in')
|
|
7
|
+
const plugins = []
|
|
8
|
+
if (cds.env.plugins) {
|
|
9
|
+
for (let each of cds.env.plugins) {
|
|
10
|
+
let impl = typeof each === 'string' ? each : each.impl
|
|
11
|
+
if (impl.startsWith('.'))
|
|
12
|
+
impl = require.resolve(cds.root + '/' + impl)
|
|
13
|
+
plugins.push(_load_plugin(impl, each))
|
|
14
|
+
}
|
|
15
|
+
} else
|
|
16
|
+
try {
|
|
17
|
+
const { dependencies } = require(cds.root + '/package.json')
|
|
18
|
+
for (let each in dependencies) {
|
|
19
|
+
plugins.push(_load_plugin(each + '/cds-plugin'))
|
|
20
|
+
}
|
|
21
|
+
} catch { /* ignored */ }
|
|
22
|
+
try { await Promise.all(plugins); } catch { /* ignored */ }
|
|
23
|
+
if (DEBUG) console.timeEnd('[cds] - loaded plugins in')
|
|
24
|
+
|
|
25
|
+
async function _load_plugin (impl, conf) {
|
|
26
|
+
// TODO support ESM plugins. But see cap/cds/pull/1838#issuecomment-1177200 !
|
|
27
|
+
const plugin = require(impl)
|
|
28
|
+
log('loaded plugin:', { impl })
|
|
29
|
+
if (plugin.activate) await plugin.activate(conf)
|
|
30
|
+
}
|
|
31
|
+
}
|
package/bin/serve.js
CHANGED
|
@@ -154,12 +154,19 @@ async function serve (all=[], o={}) {
|
|
|
154
154
|
if (o.watch) return _watch.call(this, o.project,o) // cds serve --watch <project>
|
|
155
155
|
if (o.project) _chdir_to (o.project) // cds run --project <project>
|
|
156
156
|
|
|
157
|
+
const TRACE = cds.debug('trace')
|
|
158
|
+
// if (TRACE) {
|
|
159
|
+
// TRACE?.time('load express '); require('express') // eslint-disable-line cds/no-missing-dependencies
|
|
160
|
+
// TRACE?.timeEnd('load express ')
|
|
161
|
+
// }
|
|
162
|
+
TRACE?.time('cds bootstrap ')
|
|
163
|
+
|
|
157
164
|
// Load local server.js early in order to allow setting custom cds.log.Loggers
|
|
158
165
|
const cds_server = await _local_server_js() || cds.server
|
|
159
166
|
if (!o.silent) _prepare_logging ()
|
|
160
167
|
|
|
161
168
|
// The following things are meant for dev mode, which can be overruled by feature flagse...
|
|
162
|
-
const {features} = cds.env
|
|
169
|
+
const {features,fiori} = cds.env
|
|
163
170
|
{
|
|
164
171
|
// handle --with-mocks resp. --mocked
|
|
165
172
|
if (features.with_mocks) o.mocked = _with_mocks(o)
|
|
@@ -174,23 +181,16 @@ async function serve (all=[], o={}) {
|
|
|
174
181
|
if (features.live_reload) require('../app/etc/livereload')
|
|
175
182
|
|
|
176
183
|
// add dev helper for Fiori URLs
|
|
177
|
-
if (
|
|
184
|
+
if (fiori.routes) require('../app/fiori/routes')
|
|
178
185
|
|
|
179
186
|
// add fiori preview links to default index.html
|
|
180
|
-
if (
|
|
187
|
+
if (fiori.preview) require('../app/fiori/preview')
|
|
181
188
|
|
|
182
189
|
}
|
|
183
190
|
|
|
184
191
|
// activate plugins
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
let impl = each.impl
|
|
188
|
-
if (impl.startsWith('.')) impl = require.resolve(cds.root+'/'+impl)
|
|
189
|
-
log ('loading plugin:', {impl})
|
|
190
|
-
// TODO support ESM plugins. But see cap/cds/pull/1838#issuecomment-1177200 !
|
|
191
|
-
const plugin = require(impl)
|
|
192
|
-
if (plugin.activate) await plugin.activate(each)
|
|
193
|
-
}
|
|
192
|
+
await require("./plugins")(log)
|
|
193
|
+
TRACE?.timeEnd('cds bootstrap ')
|
|
194
194
|
|
|
195
195
|
// bootstrap server from project-local server.js or from @sap/cds/server.js
|
|
196
196
|
const server = await cds_server(o)
|
|
@@ -45,7 +45,7 @@ function unfold_csn (m) { // NOSONAR
|
|
|
45
45
|
const pass2 = []
|
|
46
46
|
|
|
47
47
|
const _conf = env.requires.db || env.requires.sql || env.requires.kinds && env.requires.kinds.sql
|
|
48
|
-
const _on_sqlite = _conf.kind === 'sqlite' || _conf.
|
|
48
|
+
const _on_sqlite = _conf.kind === 'sqlite' || _conf.dialect === 'sqlite'
|
|
49
49
|
const _locales = _on_sqlite && _locales_4sql.sqlite
|
|
50
50
|
|
|
51
51
|
// Pass 1 - add localized.<locale> entities and views
|
|
@@ -19,7 +19,13 @@ function _isCompositionBacklink(e) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
const
|
|
22
|
+
const IGNORED_ENTITY_ANNOTATIONS = new Set([
|
|
23
|
+
'@readonly',
|
|
24
|
+
'@insertonly',
|
|
25
|
+
'@restrict',
|
|
26
|
+
])
|
|
27
|
+
|
|
28
|
+
const IGNORED_ELEMENT_ANNOTATIONS = [
|
|
23
29
|
'@assert.range',
|
|
24
30
|
'@assert.enum',
|
|
25
31
|
'@assert.format',
|
|
@@ -27,14 +33,21 @@ const IGNORED_ANNOTATIONS = [
|
|
|
27
33
|
'@mandatory',
|
|
28
34
|
'@Core.Immutable',
|
|
29
35
|
'@readonly',
|
|
30
|
-
'@cds.on.update',
|
|
31
|
-
'@cds.on.insert',
|
|
32
36
|
'@Core.Computed',
|
|
33
37
|
'@Common.FieldControl.Readonly',
|
|
34
38
|
'@Common.FieldControl.Mandatory',
|
|
35
39
|
'@FieldControl.Mandatory',
|
|
36
40
|
'@FieldControl.ReadOnly',
|
|
37
|
-
'@Common.FieldControl'
|
|
41
|
+
'@Common.FieldControl',
|
|
42
|
+
'@PersonalData.DataSubjectRole',
|
|
43
|
+
'@PersonalData.EntitySemantics',
|
|
44
|
+
'@PersonalData.IsPotentiallyPersonal',
|
|
45
|
+
'@PersonalData.IsPotentiallySensitive',
|
|
46
|
+
'@PersonalData.FieldSemantics'
|
|
47
|
+
// These are still needed:
|
|
48
|
+
// '@odata.etag',
|
|
49
|
+
// '@cds.on.update',
|
|
50
|
+
// '@cds.on.insert',
|
|
38
51
|
]
|
|
39
52
|
|
|
40
53
|
module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
@@ -96,7 +109,10 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
|
96
109
|
Object.defineProperty(active, 'drafts', { value: draft })
|
|
97
110
|
Object.defineProperty(draft, 'actives', { value: active })
|
|
98
111
|
draft['@cds.persistence.table'] = _draftEntity
|
|
99
|
-
|
|
112
|
+
|
|
113
|
+
for (const key in draft) {
|
|
114
|
+
if (IGNORED_ENTITY_ANNOTATIONS.has(key) || key.startsWith('@Capabilities') || key.startsWith('@PersonalData')) draft[key] = undefined
|
|
115
|
+
}
|
|
100
116
|
// Recursively add drafts for compositions
|
|
101
117
|
for (const each in draft.elements) {
|
|
102
118
|
const e = draft.elements[each]
|
|
@@ -106,7 +122,7 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
|
106
122
|
_redirect(newEl, draftEntity(e._target, model))
|
|
107
123
|
}
|
|
108
124
|
newEl.parent = draft
|
|
109
|
-
for (const ignoredAnno of
|
|
125
|
+
for (const ignoredAnno of IGNORED_ELEMENT_ANNOTATIONS) {
|
|
110
126
|
if (newEl[ignoredAnno]) newEl[ignoredAnno] = undefined
|
|
111
127
|
}
|
|
112
128
|
draft.elements[each] = newEl
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
const cds = require ('../../index')
|
|
2
|
+
const TRACE = cds.debug('trace')
|
|
2
3
|
|
|
3
4
|
module.exports = function cds_compile_for_nodejs (csn,o) {
|
|
4
5
|
if ('_4nodejs' in csn) return csn._4nodejs
|
|
6
|
+
TRACE?.time('cds.compile 4n ')
|
|
5
7
|
let dsn = csn // cds.minify (csn)
|
|
6
8
|
dsn = cds.compile.for.drafts (csn,o) //> creates a partial copy -> avoid any cds.linked() before
|
|
7
9
|
dsn = cds.compile._localized.unfold_csn (dsn)
|
|
8
10
|
dsn = cds.linked (dsn)
|
|
9
|
-
if (cds.env.
|
|
11
|
+
if (cds.env.fiori.lean_draft) cds.compile.for.lean_drafts(dsn, o)
|
|
10
12
|
Object.defineProperty (csn, '_4nodejs', {value:dsn})
|
|
11
13
|
Object.defineProperty (dsn, '_4nodejs', {value:dsn})
|
|
14
|
+
TRACE?.timeEnd('cds.compile 4n ')
|
|
12
15
|
return dsn
|
|
13
16
|
}
|
package/lib/compile/load.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const cds = require('..')
|
|
2
|
-
|
|
2
|
+
const TRACE = cds.debug('trace')
|
|
3
3
|
|
|
4
4
|
module.exports = exports = function cds_load (files, options) {
|
|
5
5
|
const all = cds.resolve(files,options)
|
|
6
|
-
if (!all)
|
|
6
|
+
if (!all) return Promise.reject (new cds.error ({
|
|
7
7
|
message: `Couldn't find a CDS model for '${files}' in ${cds.root}`,
|
|
8
8
|
code: 'MODEL_NOT_FOUND', files,
|
|
9
9
|
}))
|
|
@@ -13,6 +13,7 @@ module.exports = exports = function cds_load (files, options) {
|
|
|
13
13
|
|
|
14
14
|
exports.parsed = function cds_get (files, options, _flavor) { // NOSONAR
|
|
15
15
|
|
|
16
|
+
TRACE?.time('cds.load model ')
|
|
16
17
|
const o = typeof options === 'string' ? { flavor:options } : options || {}
|
|
17
18
|
if (!files) files = ['*']; else if (!Array.isArray(files)) files = [files]
|
|
18
19
|
if (o.files || o.flavor === 'files') return cds.resolve(files,o)
|
|
@@ -31,6 +32,7 @@ exports.parsed = function cds_get (files, options, _flavor) { // NOSONAR
|
|
|
31
32
|
|
|
32
33
|
const _finalize = (csn,o) => {
|
|
33
34
|
if (!o.silent) cds.emit ('loaded', csn)
|
|
35
|
+
TRACE?.timeEnd('cds.load model ')
|
|
34
36
|
return csn
|
|
35
37
|
}
|
|
36
38
|
|
package/lib/core/index.js
CHANGED
|
@@ -50,27 +50,21 @@ const roots = _roots ({
|
|
|
50
50
|
/** Construct builtin.types as dictionary of all roots and common types */
|
|
51
51
|
const types = _common ({ __proto__: roots,
|
|
52
52
|
UUID: {type:'string',length:36,isUUID:true},
|
|
53
|
+
String: {type:'string'}, LargeString: {type:'String'},
|
|
54
|
+
Binary: {type:'string'}, LargeBinary: {type:'Binary'},
|
|
53
55
|
Boolean: {type:'boolean'},
|
|
54
56
|
Integer: {type:'number'},
|
|
55
57
|
UInt8: {type:'Integer'},
|
|
56
|
-
Int16: {type:'Integer'},
|
|
57
|
-
Int32: {type:'Integer'},
|
|
58
|
-
Int64: {type:'Integer'},
|
|
59
|
-
Integer16: {type:'Int16'},
|
|
60
|
-
Integer32: {type:'Int32'},
|
|
61
|
-
Integer64: {type:'Int64'},
|
|
62
|
-
Decimal: {type:'number'},
|
|
63
|
-
DecimalFloat: {type:'number'},
|
|
58
|
+
Int16: {type:'Integer'}, Integer16: {type:'Int16'},
|
|
59
|
+
Int32: {type:'Integer'}, Integer32: {type:'Int32'},
|
|
60
|
+
Int64: {type:'Integer'}, Integer64: {type:'Int64'},
|
|
64
61
|
Float: {type:'number'},
|
|
65
|
-
Double: {type:'
|
|
66
|
-
|
|
62
|
+
Double: {type:'Float'},
|
|
63
|
+
Decimal: {type:'Float'}, DecimalFloat: {type:'Decimal'},
|
|
67
64
|
Date: {type:'date'},
|
|
68
65
|
Time: {type:'date'},
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
Binary: {type:'string'},
|
|
72
|
-
LargeString: {type:'string'},
|
|
73
|
-
LargeBinary: {type:'string'}
|
|
66
|
+
DateTime: {type:'date'},
|
|
67
|
+
Timestamp: {type:'DateTime'},
|
|
74
68
|
})
|
|
75
69
|
|
|
76
70
|
/**
|
|
@@ -90,5 +84,31 @@ function _common (defs) {
|
|
|
90
84
|
return prefixed
|
|
91
85
|
}
|
|
92
86
|
|
|
87
|
+
;(
|
|
88
|
+
/**
|
|
89
|
+
* Adds convenience functions which can be used like that:
|
|
90
|
+
* ```js
|
|
91
|
+
* var { Date, Time, DateTime } = cds.builtin.types
|
|
92
|
+
* DateTime.now() //> 2023-02-10T14:41:36.218Z
|
|
93
|
+
* Date.now() //> 2023-02-10T14:41:36.218Z
|
|
94
|
+
* Time.now() //> 14:43:18
|
|
95
|
+
* Date.today() //> 2023-02-10
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
function _add_convenience_functions(){
|
|
99
|
+
Object.defineProperties (types.Date, {
|
|
100
|
+
today: { value: ()=> (new Date).toISOString().slice(0,10) },
|
|
101
|
+
now: { value: ()=> (new Date).toISOString() },
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
Object.defineProperties (types.Time, {
|
|
105
|
+
now: { value: ()=> (new Date).toISOString().slice(11,19) },
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
Object.defineProperties (types.DateTime, {
|
|
109
|
+
now: { value: ()=> (new Date).toISOString() },
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
)()
|
|
93
113
|
|
|
94
114
|
module.exports = { types, classes }
|
package/lib/dbs/cds-deploy.js
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
|
-
const cds = require('../index'), { local
|
|
1
|
+
const cds = require('../index'), { local } = cds.utils
|
|
2
|
+
const COLORS = !!process.stdout.isTTY && !!process.stderr.isTTY
|
|
3
|
+
const GREY = COLORS ? '\x1b[2m' : ''
|
|
4
|
+
const RESET = COLORS ? '\x1b[0m' : ''
|
|
2
5
|
const DEBUG = cds.debug('deploy')
|
|
3
|
-
|
|
4
|
-
const colors = !!process.stdout.isTTY && !!process.stderr.isTTY
|
|
5
|
-
const term = {
|
|
6
|
-
x1b2: colors ? '\x1b[2m' : '',
|
|
7
|
-
x1b0: colors ? '\x1b[0m' : '',
|
|
8
|
-
info: colors ? (s => term.x1b2 + s + term.x1b0) : (s => s)
|
|
9
|
-
}
|
|
6
|
+
const TRACE = cds.debug('trace')
|
|
10
7
|
|
|
11
8
|
/**
|
|
12
9
|
* Implementation of `cds.deploy` common to all databases.
|
|
@@ -18,18 +15,10 @@ exports = module.exports = function cds_deploy (model,options,csvs) { return {
|
|
|
18
15
|
|
|
19
16
|
/** @param {cds.Service} db */
|
|
20
17
|
async to (db, o = options || cds.options || {}) {
|
|
21
|
-
|
|
22
|
-
const LOG = o.silent || !cds.log('deploy')._info ? ()=>{} : console.log
|
|
18
|
+
TRACE?.time ('cds.deploy db ')
|
|
23
19
|
|
|
24
|
-
if (model
|
|
25
|
-
|
|
26
|
-
if (DEBUG) try {
|
|
27
|
-
DEBUG (`loaded model from ${model.$sources.length} file(s):\n${term.x1b2}`)
|
|
28
|
-
for (let each of model.$sources) console.log (' ', local(each))
|
|
29
|
-
} finally {
|
|
30
|
-
console.log (term.x1b0)
|
|
31
|
-
}
|
|
32
|
-
}
|
|
20
|
+
if (!model) throw new Error('Must provide a model or a path to model, received: ' + model)
|
|
21
|
+
if (model && !model.definitions) model = await cds.load(model).then(cds.minify)
|
|
33
22
|
|
|
34
23
|
if (o.mocked) exports.include_external_entities_in (model)
|
|
35
24
|
else exports.exclude_external_entities_in (model)
|
|
@@ -39,18 +28,18 @@ exports = module.exports = function cds_deploy (model,options,csvs) { return {
|
|
|
39
28
|
if (!db.model) db.model = model
|
|
40
29
|
|
|
41
30
|
// create tables & views...
|
|
31
|
+
const LOG = o.silent || !cds.log('deploy')._info ? ()=>{} : console.log
|
|
42
32
|
const any = await exports.create (db,model,o)
|
|
43
33
|
if (!any && !csvs) return db
|
|
44
34
|
|
|
45
35
|
// fill in initial data...
|
|
46
|
-
await exports.init (db,model,o,csvs, file => LOG(
|
|
47
|
-
term.info(` > init from ${local(file)}`)
|
|
48
|
-
))
|
|
36
|
+
await exports.init (db,model,o,csvs, file => LOG (GREY,` > init from ${local(file)}`, RESET))
|
|
49
37
|
|
|
50
38
|
// done
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
39
|
+
let url = db.url4 (cds.context?.tenant); if (url === ':memory:') url = 'in-memory database.'
|
|
40
|
+
LOG ('/> successfully deployed to', url, '\n')
|
|
41
|
+
|
|
42
|
+
TRACE?.timeEnd ('cds.deploy db ')
|
|
54
43
|
return db
|
|
55
44
|
},
|
|
56
45
|
|
|
@@ -105,127 +94,104 @@ exports.exclude_external_entities_in = function (csn) { // NOSONAR
|
|
|
105
94
|
}
|
|
106
95
|
}
|
|
107
96
|
|
|
108
|
-
function getSqls(db, csn, o, beforeCsn) {
|
|
109
|
-
const schemaEvo = (db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto')
|
|
110
|
-
if (schemaEvo) {
|
|
111
|
-
const { afterImage: afterCsn, drops, createsAndAlters: creas } = cds.compile.to.sql.delta (csn, o, beforeCsn);
|
|
112
|
-
if(beforeCsn === undefined) {
|
|
113
|
-
// If this is the first deployment done with automatic schema evolution, generate everything as if it was a drop create
|
|
114
|
-
// but set the afterCsn in the db so we can calculate a delta going forward
|
|
115
|
-
return { afterCsn, drops: creas.map (each => {
|
|
116
|
-
let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) "?([^\s"(]+)/im) || []
|
|
117
|
-
return `DROP ${kind} IF EXISTS ${entity};`
|
|
118
|
-
}).reverse(), creas };
|
|
119
|
-
}
|
|
120
|
-
return { afterCsn, drops, creas };
|
|
121
|
-
} else {
|
|
122
|
-
const creas = cds.compile.to.sql(csn, o);
|
|
123
|
-
const drops = creas.map (each => {
|
|
124
|
-
let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) "?([^\s"(]+)/im) || []
|
|
125
|
-
return `DROP ${kind} IF EXISTS ${entity};`
|
|
126
|
-
}).reverse();
|
|
127
|
-
return { afterCsn: {}, drops, creas };
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
97
|
|
|
131
|
-
exports.create = async function (db, csn=db.model, o) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
98
|
+
exports.create = async function cds_deploy_create (db, csn=db.model, o) {
|
|
99
|
+
|
|
100
|
+
let drops, creas, schevo = db.options?.schema_evolution === 'auto' || o.schema_evolution === 'auto'
|
|
101
|
+
if (schevo) {
|
|
102
|
+
// REVISIT: replace by db-specific ways to read/updated recent deployed model
|
|
103
|
+
let before = await db.run (async tx => { try {
|
|
104
|
+
let [{ csn }] = await tx.run('SELECT csn from cds_Model')
|
|
105
|
+
return JSON.parse(csn)
|
|
106
|
+
} catch(e) { if (!e.message.includes('no such table')) throw e
|
|
107
|
+
await tx.run(`CREATE table cds_Model (csn CLOB)`)
|
|
108
|
+
await tx.run(`INSERT into cds_Model values ('null')`)
|
|
109
|
+
}})
|
|
110
|
+
const { afterImage, drops:_drops, createsAndAlters:_creas } = cds.compile.to.sql.delta (csn, o, before)
|
|
111
|
+
creas = _creas.concat (o.dry ? [] : UPDATE('cds.Model').with({ csn: JSON.stringify(afterImage) }))
|
|
112
|
+
drops = before ? _drops : _drops4(_creas)
|
|
113
|
+
} else {
|
|
114
|
+
creas = cds.compile.to.sql (csn, o)
|
|
115
|
+
drops = _drops4(creas) .concat ('DROP table if exists cds_Model;')
|
|
137
116
|
}
|
|
117
|
+
if (!creas || creas.length === 0) return
|
|
138
118
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (e.message.includes('no such table: cds_Model')) {
|
|
145
|
-
await db.run('CREATE table cds_Model ( csn CLOB )')
|
|
146
|
-
await db.insert({csn:''}).into('cds.Model')
|
|
147
|
-
}
|
|
119
|
+
function _drops4 (creas) {
|
|
120
|
+
return creas.map (each => {
|
|
121
|
+
let [, kind, entity] = each.match(/^CREATE (TABLE|VIEW) ("[^"]+"|[^\s(]+)/im) || []
|
|
122
|
+
return `DROP ${kind} IF EXISTS ${entity};`
|
|
123
|
+
}).reverse()
|
|
148
124
|
}
|
|
149
125
|
|
|
150
|
-
const { afterCsn, drops, creas } = getSqls(db, csn, o, beforeCsn);
|
|
151
|
-
|
|
152
|
-
if (!creas || creas.length === 0) return
|
|
153
126
|
if (o.dry) {
|
|
154
|
-
console.log(); for (let each of drops) console.log(each
|
|
127
|
+
console.log(); for (let each of drops) console.log(each)
|
|
155
128
|
console.log(); for (let each of creas) console.log(each,'\n')
|
|
156
|
-
|
|
157
|
-
|
|
129
|
+
}
|
|
130
|
+
else return db.run (async tx => {
|
|
131
|
+
// This runs in a new transaction if called from CLI, while joining
|
|
132
|
+
// existing root tx, e.g. when called from DeploymentService.
|
|
133
|
+
if (!schevo && await tx.exists('sqlite.schema').where({name:'cds_xt_Extensions'})) {
|
|
134
|
+
// Poor man's schema evolution for extensions in drop-create deployments
|
|
135
|
+
drops = drops.filter(d => !d.includes('cds_xt_Extensions'))
|
|
136
|
+
creas = creas.filter(c => !c.includes('cds_xt_Extensions'))
|
|
137
|
+
}
|
|
138
|
+
// Set the context model while deploying for cqn42sql in new db layers
|
|
139
|
+
tx.model = cds.compile.for.nodejs(csn)
|
|
158
140
|
await tx.run(drops)
|
|
159
141
|
await tx.run(creas)
|
|
160
|
-
if (schemaEvo) {
|
|
161
|
-
await tx.update('cds.Model').with({ csn: JSON.stringify(afterCsn) })
|
|
162
|
-
}
|
|
163
142
|
return true
|
|
164
143
|
})
|
|
165
144
|
}
|
|
166
145
|
|
|
167
146
|
|
|
168
|
-
exports.init = (db, csn=db.model, o,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const schemaEvo = db.options?.schema_evolution === 'auto' || o?.schema_evolution === 'auto'
|
|
172
|
-
const resources = await exports.resources(csn, {testdata: cds.env.features.test_data})
|
|
173
|
-
const inits=[]
|
|
174
|
-
|
|
175
|
-
if (csvs) {
|
|
176
|
-
const ccsn = cds.compile.for['nodejs'](csn) // compile to calculate keys for newly added entities
|
|
177
|
-
for(let [file,src] of Object.entries(csvs)) {
|
|
178
|
-
const entity = _entity4(path.basename(file, '.csv'), csn)
|
|
179
|
-
if (entity?.name) {
|
|
180
|
-
const q = INSERT_from_csv (entity.name,src,schemaEvo); if (!q) continue
|
|
181
|
-
if (db.kind === 'better-sqlite') _add_missing_pks2(q)
|
|
182
|
-
q._target = ccsn.definitions[entity.name]
|
|
183
|
-
inits.push (tx.run(q) .catch (e => {
|
|
184
|
-
throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
|
|
185
|
-
}))
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
} else {
|
|
189
|
-
for (let [file,e] of Object.entries(resources)) {
|
|
190
|
-
if (e === '*') { // init.js/ts
|
|
191
|
-
let x = await cds.utils._import(file); if (!x) continue
|
|
192
|
-
if (x.default) x = x.default // default ESM export
|
|
193
|
-
inits.push (!x.then && typeof x === 'function' ? x(tx,csn) : x)
|
|
194
|
-
log (file)
|
|
195
|
-
} else { // from .csv or .json
|
|
196
|
-
const INSERT_into = _from_csv_or_json [path.extname(file)]
|
|
197
|
-
const src = await read(file,'utf8'); if (!src) continue
|
|
198
|
-
const q = INSERT_into (e,src,schemaEvo); if (!q) continue
|
|
199
|
-
if (db.kind === 'better-sqlite') _add_missing_pks2(q)
|
|
200
|
-
if (cds.requires['cds.xt.ModelProviderService']?.kind === 'in-sidecar') q._target = csn.definitions[e]
|
|
201
|
-
log (file,e)
|
|
202
|
-
inits.push (tx.run(q) .catch (e => {
|
|
203
|
-
throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ inspect(q) })
|
|
204
|
-
}))
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
}
|
|
147
|
+
exports.init = async function cds_deploy_init (db, csn=db.model, o, srces, log=()=>{}) {
|
|
148
|
+
const t = cds.context?.tenant; if (t && t === cds.requires.multitenancy?.t0) return
|
|
149
|
+
return db.run (async tx => {
|
|
208
150
|
|
|
209
|
-
|
|
151
|
+
const m = tx.model = cds.compile.for.nodejs(csn) //> use correct model while deploying
|
|
152
|
+
const data = await exports.data (m,srces)
|
|
153
|
+
const query = _queries4 (db,m)
|
|
154
|
+
const INSERT_from = INSERT_from4 (db,o)
|
|
210
155
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
156
|
+
for await (let [ file, entity, src ] of data) {
|
|
157
|
+
log (file)
|
|
158
|
+
if (entity) {
|
|
159
|
+
const q = INSERT_from (file) .into (entity, src)
|
|
160
|
+
if (q) try { await tx.run (query(q)) } catch(e) {
|
|
161
|
+
throw Object.assign (e, { message: 'in cds.deploy(): ' + e.message +'\n'+ cds.utils.inspect(q) })
|
|
162
|
+
}
|
|
163
|
+
} else { //> init.js/ts case
|
|
164
|
+
if (typeof src === 'function') src(tx,csn)
|
|
219
165
|
}
|
|
220
166
|
}
|
|
221
|
-
}
|
|
167
|
+
})
|
|
168
|
+
}
|
|
222
169
|
|
|
223
|
-
|
|
170
|
+
|
|
171
|
+
/** Prepare input from .csv, .json, init.js, ... */
|
|
172
|
+
exports.data = async function cds_deploy_prepare_data (csn, srces) {
|
|
173
|
+
// In case of extension deployment .csv or .json input are provided through argument `srces`.
|
|
174
|
+
if (srces) return Object.entries(srces) .map (([file, src]) => {
|
|
175
|
+
let e = _entity4 (path.basename(file,'.csv'), csn)
|
|
176
|
+
return [ file, e, src ]
|
|
177
|
+
})
|
|
178
|
+
// If not, we load them from cds.deploy.resources(csn)
|
|
179
|
+
const resources = await exports.resources(csn, { testdata: cds.env.features.test_data })
|
|
180
|
+
return Object.entries(resources) .map (async ([file,e]) => {
|
|
181
|
+
if (e === '*') {
|
|
182
|
+
let init_js = await cds.utils._import (file)
|
|
183
|
+
return [ file, null, init_js.default || init_js ]
|
|
184
|
+
} else {
|
|
185
|
+
let src = await read (file, 'utf8')
|
|
186
|
+
return [ file, e, src ]
|
|
187
|
+
}
|
|
188
|
+
})
|
|
189
|
+
}
|
|
224
190
|
|
|
225
191
|
|
|
226
|
-
exports.resources = async function (csn, opts) {
|
|
192
|
+
exports.resources = async function cds_deploy_resources (csn, opts) {
|
|
227
193
|
if (!csn || !csn.definitions) csn = await cds.load (csn||'*') .then (cds.minify)
|
|
228
|
-
const folders = await
|
|
194
|
+
const folders = await cds_deploy_resources.folders(csn, opts)
|
|
229
195
|
const found={}, ts = process.env.CDS_TYPESCRIPT
|
|
230
196
|
for (let folder of folders) {
|
|
231
197
|
// fetching init.js files
|
|
@@ -237,7 +203,7 @@ exports.resources = async function (csn, opts) {
|
|
|
237
203
|
const files = await readdir (subdir)
|
|
238
204
|
for (let fx of files) {
|
|
239
205
|
if (fx[0] === '-') continue
|
|
240
|
-
const ext = path.extname(fx); if (ext in
|
|
206
|
+
const ext = path.extname(fx); if (ext in {'.csv':1,'.json':2}) {
|
|
241
207
|
const f = fx.slice(0,-ext.length)
|
|
242
208
|
if (/[._]texts$/.test(f) && files.some(g => g.startsWith(f+'_'))) {
|
|
243
209
|
// ignores 'Books_texts.csv/json' if there is any 'Books_texts_LANG.csv/json'
|
|
@@ -287,17 +253,47 @@ const _entity4 = (file,csn) => {
|
|
|
287
253
|
return entity.name ? entity : { name, __proto__:entity }
|
|
288
254
|
}
|
|
289
255
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
256
|
+
|
|
257
|
+
/** Prepare special handling for new db services */
|
|
258
|
+
const _queries4 = (db,csn) => !db.cqn2sql ? q => q : q => {
|
|
259
|
+
const { columns, rows } = q.INSERT || q.UPSERT; if (!columns) return q // REVISIT: .entries are covered by current runtime -> should eventually also be handled here
|
|
260
|
+
const entity = csn.definitions[q._target.name]
|
|
261
|
+
|
|
262
|
+
// Fill in missing primary keys...
|
|
263
|
+
const { uuid } = cds.utils
|
|
264
|
+
for (let k in entity.keys) if (entity.keys[k].isUUID && !columns.includes(k)) {
|
|
265
|
+
columns.push(k)
|
|
266
|
+
rows.forEach(row => row.push(uuid()))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Fill in missing managed data...
|
|
270
|
+
const pseudos = { $user: 'anonymous', $now: (new Date).toISOString() }
|
|
271
|
+
for (let k in entity.elements) {
|
|
272
|
+
const managed = entity.elements[k]['@cds.on.insert']?.['=']
|
|
273
|
+
if (managed && !columns.includes(k)) {
|
|
274
|
+
columns.push(k)
|
|
275
|
+
rows.forEach(row => row.push(pseudos[managed]))
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return q
|
|
293
280
|
}
|
|
294
281
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
282
|
+
|
|
283
|
+
const INSERT_from4 = (db,o) => {
|
|
284
|
+
const schevo = db.options?.schema_evolution === 'auto' || o?.schema_evolution === 'auto'
|
|
285
|
+
const INSERT_into = (schevo ? UPSERT : INSERT).into
|
|
286
|
+
return (file) => ({
|
|
287
|
+
'.json': { into (entity, json) {
|
|
288
|
+
let records = JSON.parse(json)
|
|
289
|
+
if (records.length > 0) return INSERT_into(entity).entries(records)
|
|
290
|
+
}},
|
|
291
|
+
'.csv': { into (entity, csv) {
|
|
292
|
+
let [cols, ...rows] = cds.parse.csv(csv)
|
|
293
|
+
if (rows.length > 0) return INSERT_into(entity).columns(cols).rows(rows)
|
|
294
|
+
}},
|
|
295
|
+
}) [path.extname(file)]
|
|
298
296
|
}
|
|
299
297
|
|
|
300
|
-
const _from_csv_or_json = { '.json': INSERT_from_json, '.csv': INSERT_from_csv, }
|
|
301
298
|
const _skip = e => !e || e['@cds.persistence.skip'] === true
|
|
302
|
-
|
|
303
299
|
/* eslint-disable no-console */
|