@sap/cds 6.1.0 → 6.1.3

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 (64) hide show
  1. package/CHANGELOG.md +67 -0
  2. package/apis/log.d.ts +112 -36
  3. package/apis/services.d.ts +13 -2
  4. package/bin/build/provider/buildTaskHandlerFeatureToggles.js +2 -3
  5. package/bin/build/provider/hana/index.js +4 -2
  6. package/bin/build/provider/mtx/resourcesTarBuilder.js +4 -8
  7. package/bin/deploy/to-hana/hana.js +20 -25
  8. package/bin/deploy/to-hana/hdiDeployUtil.js +13 -2
  9. package/lib/dbs/cds-deploy.js +2 -2
  10. package/lib/env/schemas/cds-rc.json +10 -1
  11. package/lib/index.js +1 -1
  12. package/lib/log/format/kibana.js +19 -1
  13. package/lib/ql/Query.js +9 -3
  14. package/lib/ql/SELECT.js +1 -1
  15. package/lib/ql/UPDATE.js +2 -2
  16. package/lib/ql/cds-ql.js +4 -10
  17. package/lib/req/context.js +15 -11
  18. package/lib/srv/srv-api.js +8 -0
  19. package/lib/srv/srv-dispatch.js +11 -7
  20. package/lib/srv/srv-models.js +4 -3
  21. package/lib/srv/srv-tx.js +52 -40
  22. package/lib/utils/cds-utils.js +3 -3
  23. package/lib/utils/resources/index.js +5 -5
  24. package/lib/utils/resources/tar.js +1 -1
  25. package/libx/_runtime/auth/index.js +2 -2
  26. package/libx/_runtime/cds-services/adapter/odata-v4/handlers/read.js +2 -1
  27. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-commons/utils/PrimitiveValueDecoder.js +0 -2
  28. package/libx/_runtime/cds-services/adapter/odata-v4/okra/odata-server/deserializer/DeserializerFactory.js +3 -1
  29. package/libx/_runtime/cds-services/adapter/odata-v4/utils/readAfterWrite.js +0 -2
  30. package/libx/_runtime/cds-services/services/utils/compareJson.js +3 -1
  31. package/libx/_runtime/cds-services/util/assert.js +3 -0
  32. package/libx/_runtime/common/generic/input.js +17 -2
  33. package/libx/_runtime/common/generic/put.js +4 -1
  34. package/libx/_runtime/common/generic/temporal.js +0 -3
  35. package/libx/_runtime/common/utils/binary.js +3 -4
  36. package/libx/_runtime/common/utils/keys.js +14 -6
  37. package/libx/_runtime/common/utils/propagateForeignKeys.js +122 -0
  38. package/libx/_runtime/common/utils/resolveView.js +1 -1
  39. package/libx/_runtime/common/utils/template.js +2 -3
  40. package/libx/_runtime/db/expand/expandCQNToJoin.js +1 -1
  41. package/libx/_runtime/db/expand/rawToExpanded.js +7 -6
  42. package/libx/_runtime/db/generic/input.js +7 -4
  43. package/libx/_runtime/db/sql-builder/InsertBuilder.js +1 -1
  44. package/libx/_runtime/extensibility/add.js +3 -0
  45. package/libx/_runtime/extensibility/handler/transformREAD.js +20 -18
  46. package/libx/_runtime/extensibility/push.js +11 -11
  47. package/libx/_runtime/extensibility/token.js +2 -1
  48. package/libx/_runtime/extensibility/utils.js +8 -6
  49. package/libx/_runtime/fiori/generic/new.js +1 -3
  50. package/libx/_runtime/fiori/generic/patch.js +1 -7
  51. package/libx/_runtime/fiori/utils/where.js +1 -1
  52. package/libx/_runtime/messaging/common-utils/authorizedRequest.js +1 -1
  53. package/libx/_runtime/messaging/enterprise-messaging-utils/EMManagement.js +1 -2
  54. package/libx/_runtime/remote/utils/client.js +29 -10
  55. package/libx/_runtime/sqlite/Service.js +7 -5
  56. package/libx/_runtime/sqlite/execute.js +41 -28
  57. package/libx/odata/cqn2odata.js +6 -2
  58. package/libx/rest/RestAdapter.js +3 -6
  59. package/libx/rest/middleware/input.js +2 -3
  60. package/package.json +1 -1
  61. package/srv/extensibility-service.cds +4 -3
  62. package/srv/model-provider.js +1 -1
  63. package/srv/mtx.js +18 -9
  64. package/libx/_runtime/db/utils/propagateForeignKeys.js +0 -93
package/CHANGELOG.md CHANGED
@@ -4,6 +4,73 @@
4
4
  - The format is based on [Keep a Changelog](http://keepachangelog.com/).
5
5
  - This project adheres to [Semantic Versioning](http://semver.org/).
6
6
 
7
+ ## Version 6.1.3 - 2022-09-13
8
+
9
+ ### Added
10
+
11
+ - Configuration to change maximum body size in bytes for remote requests: `cds.env.remote.max_body_length: 1000` sets it to 1 MB
12
+
13
+ ### Changed
14
+
15
+ - For structured input, foreign keys are generated as non-enumerable properties on application-service layer
16
+
17
+ ### Fixed
18
+
19
+ - Deep insert/update/upsert requests where the key of an association is provided in a structured format will not be rejected anymore if the target has default or mandatory fields
20
+ - For some configurations, mtxs services were bootstrapped twice
21
+
22
+ ## Version 6.1.2 - 2022-09-05
23
+
24
+ ### Fixed
25
+
26
+ - Missing key insertion from where clauses in references for deep update statements
27
+ - Prevent duplicate entries for some `INSERT` statements
28
+ - Log details were not properly displayed in Kibana
29
+ - getCsn in model-provider if `cds.requires.toggles` is false
30
+ - HTTP calls in messaging have the correct content length
31
+ - Performance issue for OData <entity>/$count requests
32
+ - Typescript definition for SQL-native variant of `srv.run`, like `srv.run('SELECT * from Authors where name like ?',['%Poe%'])`
33
+ - Typescript definitions for `srv.run( [query] )` and `srv.send( {query, headers} )`
34
+ - Typescript definitions for `cds.log` are improved, level indicators like `cds.log()._debug` added
35
+ - Typescript definitions for `tx` now carry additional service methods
36
+ - `cds login` now returns errors with a better root cause messages
37
+ - `$expand` requests for to-one associations that do not select the foreign key
38
+ - `UPDATE` statement accepts empty objects: `UPDATE('Foo').with({ bar: {} })`
39
+ - URI encoding of parameters in remote service calls
40
+
41
+ ### Removed
42
+
43
+ ## Version 6.1.1 - 2022-08-24
44
+
45
+ ### Added
46
+
47
+ - The configuration schema now includes `cds.extends` and `new-fields` (in `cds.xt.ExtensibilityService`)
48
+ - `srv.run(fn)` now accepts a function as first argument, which will be run in an active outer transaction, if any, or in a newly created one. This is in contrast to `srv.tx(fn)` which always creates a new tx.
49
+ ```js
50
+ cds.run (tx => { // nested operations are guaranteed to run in a tx
51
+ await INSERT.into (Foo, ...)
52
+ await INSERT.into (Bar, ...)
53
+ })
54
+ ```
55
+
56
+ ### Fixed
57
+
58
+ - Erroneous checks for `join` in `SELECT.from(SELECT.from('xxx'))`
59
+ - Virtual fields with default values in draft context
60
+ - View resolving without model information doesn't crash
61
+ - Unable to upload large attachments. Uploading a large attachment (base64 encoded) caused a runtime exception.
62
+ - `cds.Query.then()` is using `AsyncResource.runInAsyncScope` from now on. &rarr; this avoids callstacks being cut off, e.g. in debuggers.
63
+ - `cds.tx()` and `cds.context` have been fixed to avoid accidential fallbacks to auto-commit mode.
64
+ - HDI configuration data (e.g. `./cfg`, `.hdiignore`) is now included in the `resources.tgz` file which is required for Streamlined MTX.
65
+ - `cds deploy` accepts in addition to `VCAP_SERVICES` also `TARGET_CONTAINER` and `SERVICE_REPLACEMENTS` from vcap file when using `--vcap-file` parameter.
66
+ - `cds build` doesn't duplicate CSV files that are contained in `db/src/**`.
67
+ - Typescript issues in `apis/log.d.ts`
68
+ - `cds build` adds OS agnostic base model path to generated feature CSN.
69
+ - Unhandled promise rejection in `expand` handling
70
+ - `cds.context.model` middleware is not mounted for not extensible services
71
+ - `cds.context` continuation was sometimes not reset in REST adapter
72
+ - Requests don't fail with `RangeError: Unable to get service from service map due to error: Invalid time value` anymore
73
+
7
74
  ## Version 6.1.0 - 2022-08-10
8
75
 
9
76
  ### Added
package/apis/log.d.ts CHANGED
@@ -1,48 +1,124 @@
1
- /**
2
- * Returns a trace logger for the given module if trace is switched on for it,
3
- * otherwise returns null. All cds runtime packages use this method for their
4
- * trace and debug output.
5
- *
6
- * @see [capire](https://cap.cloud.sap/docs/node.js/cds-log)
7
- *
8
- * By default this logger would prefix all output with '[sql] - '.
9
- * You can change this by specifying another prefix in the options:
10
- *
11
- * const LOG = cds.log('sql|db',{ prefix:'cds.ql' })
12
- *
13
- * Call cds.log() for a given module again to dynamically change the log level
14
- * of all formerly created loggers, for example:
15
- *
16
- * const LOG = cds.log('sql')
17
- * LOG.info ('this will show, as default level is info')
18
- * cds.log('sql','warn')
19
- * LOG.info ('this will be suppressed now')
20
- *
21
- */
22
1
  export = cds
23
2
  declare class cds {
24
- log(name: string, options?: string | number | { level: number, prefix: string }): Logger
3
+ /**
4
+ * Create a new logger, or install a custom log formatter
5
+ */
6
+ log: LogFactory
7
+
8
+ /**
9
+ * Shortcut to `cds.log(...).debug`, returning `undefined` if `cds.log(...)._debug` is `false`.
10
+ * Use like this:
11
+ * ```
12
+ * const dbg = cds.debug('foo')
13
+ * ...
14
+ * dbg && dbg('message')
15
+ * ```
16
+ *
17
+ * @param name logger name
18
+ */
19
+ debug(name: string): undefined | Log
25
20
  }
26
21
 
27
- class Logger {
22
+ declare type LogFactory = {
28
23
  /**
29
- * Formats a log outputs by returning an array of arguments which are passed to
30
- * console.log() et al.
31
- * You can assign custom formatters like that:
24
+ * Returns a trace logger for the given module if trace is switched on for it,
25
+ * otherwise returns null. All cds runtime packages use this method for their
26
+ * trace and debug output.
32
27
  *
33
- * cds.log.format = (module, level, ...args) => [ '[', module, ']', ...args ]
28
+ * By default this logger would prefix all output with `[sql] - `
29
+ * You can change this by specifying another prefix in the options:
30
+ * ```
31
+ * const LOG = cds.log('sql|db',{ prefix:'cds.ql' })
32
+ * ```
33
+ * Call `cds.log()` for a given module again to dynamically change the log level
34
+ * of all formerly created loggers, for example:
35
+ * ```
36
+ * const LOG = cds.log('sql')
37
+ * LOG.info ('this will show, as default level is info')
38
+ * cds.log('sql','warn')
39
+ * LOG.info ('this will be suppressed now')
40
+ * ```
41
+ * @param name logger name
42
+ * @param options level and prefix
43
+ * @returns the logger
44
+ *
45
+ * @see [capire](https://cap.cloud.sap/docs/node.js/cds-log)
46
+ */
47
+ (name: string, options?: string | number | { level?: number, prefix?: string }): Logger
48
+
49
+ /**
50
+ * Set a custom formatter function like that:
51
+ * ```
52
+ * cds.log.format = (module, level, ...args) => [ '[', module, ']', ...args ]
53
+ * ```
54
+ *
55
+ * The formatter shall return an array of arguments, which are passed to the logger (for example, `console.log()`)
56
+ *
57
+ * @param module logger name
58
+ * @param level log level
59
+ * @param args additional arguments
34
60
  */
35
61
  format(module: string, level: number, args: any[]): any[]
36
- trace(message?: any, ...optionalParams: any[]): void
37
- debug(message?: any, ...optionalParams: any[]): void
38
- log(message?: any, ...optionalParams: any[]): void
39
- info(message?: any, ...optionalParams: any[]): void
40
- warn(message?: any, ...optionalParams: any[]): void
41
- error(message?: any, ...optionalParams: any[]): void
42
62
  }
43
63
 
44
- declare const levels = {
45
- SILENT: 0, ERROR: 1, WARN: 2, INFO: 3, DEBUG: 4, TRACE: 5, SILLY: 5, VERBOSE: 5
64
+ declare class Logger {
65
+ /**
66
+ * Logs with 'trace' level
67
+ */
68
+ trace: Log
69
+ /**
70
+ * Logs with 'debug' level
71
+ */
72
+ debug: Log
73
+ /**
74
+ * Logs with 'info' level
75
+ */
76
+ info: Log
77
+ /**
78
+ * Logs with 'warn' level
79
+ */
80
+ warn: Log
81
+ /**
82
+ * Logs with 'error' level
83
+ */
84
+ error: Log
85
+ /**
86
+ * Logs with default level
87
+ */
88
+ log: Log
89
+
90
+ /**
91
+ * @returns whether 'trace' level is active
92
+ */
93
+ _trace: boolean
94
+ /**
95
+ * @returns whether 'debug' level is active
96
+ */
97
+ _debug: boolean
98
+ /**
99
+ * @returns whether 'info' level is active
100
+ */
101
+ _info: boolean
102
+ /**
103
+ * @returns whether 'warn' level is active
104
+ */
105
+ _warn: boolean
106
+ /**
107
+ * @returns whether 'error' level is active
108
+ */
109
+ _error: boolean
110
+ }
111
+
112
+ declare type Log = {
113
+ /**
114
+ * Logs a message
115
+ *
116
+ * @param message text to log
117
+ * @param optionalParams additional parameters, same as in `console.log(text, param1, ...)`
118
+ */
119
+ (message?: any, ...optionalParams: any[]): void
46
120
  }
47
121
 
48
- declare const { ERROR, WARN, INFO, DEBUG, TRACE } = levels
122
+ declare enum levels {
123
+ SILENT = 0, ERROR = 1, WARN = 2, INFO = 3, DEBUG = 4, TRACE = 5, SILLY = 5, VERBOSE = 5
124
+ }
@@ -34,7 +34,7 @@ export class QueryAPI {
34
34
  /**
35
35
  * @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
36
36
  */
37
- run (query : ConstructedQuery) : Promise<ResultSet | any>
37
+ run (query : ConstructedQuery|ConstructedQuery[]) : Promise<ResultSet | any>
38
38
 
39
39
  /**
40
40
  * @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
@@ -42,6 +42,11 @@ export class QueryAPI {
42
42
  run (query : Query) : Promise<ResultSet | any>
43
43
 
44
44
  /**
45
+ * @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run-sql)
46
+ */
47
+ run (query : string, args? : any[]|object) : Promise<ResultSet | any>
48
+
49
+ /**
45
50
  * @see [docs](https://cap.cloud.sap/docs/node.js/services#srv-run)
46
51
  */
47
52
  foreach (query : Query, callback: (row:object) => void) : this
@@ -141,6 +146,12 @@ export class Service extends QueryAPI {
141
146
  send (details: { event: Events, data?: object, headers?: object }) : Promise<this>
142
147
 
143
148
  /**
149
+ * Constructs and sends a synchronous request.
150
+ * @see [capire docs](https://cap.cloud.sap/docs/node.js/services#srv-send)
151
+ */
152
+ send (details: { query: ConstructedQuery, headers?: object }) : Promise<this>
153
+
154
+ /**
144
155
  * Constructs and sends a GET request.
145
156
  * @see [capire docs](https://cap.cloud.sap/docs/node.js/services#srv-send)
146
157
  */
@@ -190,7 +201,7 @@ export class Service extends QueryAPI {
190
201
 
191
202
  }
192
203
 
193
- export interface Transaction extends QueryAPI {
204
+ export interface Transaction extends Service {
194
205
  commit() : Promise<void>
195
206
  rollback() : Promise<void>
196
207
  }
@@ -100,17 +100,16 @@ class FeatureToggleBuilder extends BuildTaskHandlerInternal {
100
100
  }
101
101
  const features = {}
102
102
  const options = { ...this.options(), flavor: 'parsed' }
103
- const srvFolder = path.relative(destFts, destBase)
104
103
 
105
104
  // create feature models
106
105
  for (const ftName in ftsPaths) {
107
106
  const ftCsn = await this.cds.load(ftsPaths[ftName], options)
107
+ const ftPath = path.join(destFts, this.ftsName, ftName)
108
108
 
109
109
  // replace require paths by base model path to ensure precedence of feature annos
110
110
  // see https://pages.github.tools.sap/cap/docs/cds/compiler-messages#anno-duplicate-unrelated-layer
111
- ftCsn.requires = [path.join('../..', srvFolder, DEFAULT_CSN_FILE_NAME)]
111
+ ftCsn.requires = [path.join(path.relative(ftPath, destBase), DEFAULT_CSN_FILE_NAME).replace(/\\/g,'/')]
112
112
 
113
- const ftPath = path.join(destFts, this.ftsName, ftName)
114
113
  await this.compileToJson(ftCsn, path.join(ftPath, DEFAULT_CSN_FILE_NAME))
115
114
  await this._validateFeature(ftPath)
116
115
  features[ftName] = ftCsn
@@ -106,8 +106,11 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
106
106
 
107
107
  // 1. copy csv files into 'src/gen/data' or 'src/gen/csv' subfolder
108
108
  const promises = []
109
+ const dbSrc = path.join(src, 'src')
110
+
109
111
  resources.forEach(res => {
110
- if (res && /\.csv$/.test(res)) {
112
+ // do not duplicate resources that are already contained in db/src/**
113
+ if (res && /\.csv$/.test(res) && !res.startsWith(dbSrc)) {
111
114
  promises.push(this.copy(res).to(path.join(this.task.options.compileDest, csvFolder, path.basename(res))))
112
115
  }
113
116
  })
@@ -118,7 +121,6 @@ class HanaModuleBuilder extends BuildTaskHandlerInternal {
118
121
  blockList += this.hasBuildOption(CONTENT_ENV, false) ? "|\\.env($|\\..*$)" : ""
119
122
  blockList += this.hasBuildOption(CONTENT_DEFAULT_ENV_JSON, false) ? "|default-env\\.json$" : ""
120
123
  blockList = new RegExp(blockList)
121
- const dbSrc = path.join(src, 'src')
122
124
 
123
125
  await this.copyNativeContent(src, dest, (entry) => {
124
126
  if (entry.startsWith(dbSrc)) {
@@ -47,20 +47,16 @@ class ResourcesTarBuilder {
47
47
  * Reads all resources for HANA deployment - 'db/src/**' and 'db/undeploy.json'.
48
48
  * - CSV files, native HANA artefacts, generated HANA artefacts, undeploy.json
49
49
  * See '../../../../lib.deploy'
50
- *
50
+ * Note: the hana build task has already been executed
51
51
  * @param {string} root
52
52
  * @returns an array containing all resources
53
53
  */
54
54
  _getHanaResources(root) {
55
- const hanaSrcFolder = path.join(root, 'src')
56
- const undeployJson = path.join(root, 'undeploy.json')
55
+ const hanaNativeFolders = [path.join(root, 'src'), path.join(root, 'cfg')]
56
+ const hanaNativeFiles = [path.join(root, 'undeploy.json'), path.join(root, '.hdiignore')]
57
57
 
58
- // hana build task has already been executed
59
58
  return BuildTaskHandlerInternal._find(root, (res) => {
60
- if (res === undeployJson) {
61
- return true
62
- }
63
- if (res.startsWith(hanaSrcFolder)) {
59
+ if (hanaNativeFolders.some(folder => res.startsWith(folder)) || hanaNativeFiles.some(file => res === file)) {
64
60
  return true
65
61
  }
66
62
  return false
@@ -35,7 +35,7 @@ class HanaDeployer {
35
35
  this.logger = logger;
36
36
 
37
37
  this.logger.log(`${bold('Starting deploy to SAP HANA ...')}`);
38
- if (vcapFile) {
38
+ if (vcapFile) {
39
39
  this.logger.log();
40
40
  this.logger.log(`Using VCAP_SERVICES from file ${vcapFile} (beta feature).`);
41
41
  bindCallback = null // credentials are given - no cds bind then
@@ -49,9 +49,9 @@ class HanaDeployer {
49
49
 
50
50
  const { buildResults } = await this._build(buildTaskOptions, model);
51
51
 
52
- let vcapServices;
52
+ let vcapFileEnv;
53
53
  if (vcapFile) {
54
- vcapServices = await this._loadVCAPServices(vcapFile);
54
+ vcapFileEnv = await this._loadDefaultEnv(vcapFile);
55
55
  }
56
56
 
57
57
  for (const buildResult of buildResults) {
@@ -71,7 +71,7 @@ class HanaDeployer {
71
71
  serviceKeyName = cfServiceInstanceKeyName;
72
72
  serviceName = serviceName || cfServiceInstanceName;
73
73
 
74
- vcapServices = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
74
+ vcapFileEnv = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
75
75
 
76
76
  if (!noSave && !bindCallback) {
77
77
  await this._addInstanceToDefaultEnvJson([currentModelFolder, projectPath], cfServiceInstanceName, serviceKey);
@@ -95,7 +95,7 @@ class HanaDeployer {
95
95
  });
96
96
  }
97
97
 
98
- await hdiDeployUtil.deploy(currentModelFolder, vcapServices, hdiOptions);
98
+ await hdiDeployUtil.deploy(currentModelFolder, vcapFileEnv, hdiOptions);
99
99
 
100
100
  if (bindCallback) {
101
101
  const args = [path.relative(projectPath, buildResult.task.src)];
@@ -204,16 +204,12 @@ class HanaDeployer {
204
204
  }
205
205
 
206
206
 
207
- async _loadVCAPServices(vcapFile) {
207
+ async _loadDefaultEnv(defaultEnvFile) {
208
208
  try {
209
- const content = JSON.parse(await fs.readFile(vcapFile));
210
- if (!content.VCAP_SERVICES) {
211
- throw new Error(`The vcap file ${vcapFile} does not contain a VCAP_SERVICES entry.`);
212
- }
213
-
214
- return content.VCAP_SERVICES;
209
+ const content = await fs.readFile(defaultEnvFile);
210
+ return JSON.parse(content);
215
211
  } catch (err) {
216
- throw new Error(`Error reading vcap file: ${err.message}`);
212
+ throw new Error(`Error reading default env file: ${err.message}`);
217
213
  }
218
214
  }
219
215
 
@@ -237,13 +233,10 @@ class HanaDeployer {
237
233
  }
238
234
 
239
235
  const hanaEntry = this._getVCAPServicesEntry(serviceInstanceName, serviceKey)
240
- defaultEnvJson.VCAP_SERVICES = {
241
- ...defaultEnvJson.VCAP_SERVICES,
242
- ...hanaEntry
243
- }
236
+ Object.assign(defaultEnvJson, hanaEntry);
244
237
 
245
238
  this.logger.log(`Writing ${defaultEnvJsonPath}`);
246
- await fs.mkdir(path.dirname(defaultEnvJsonPath), {recursive: true})
239
+ await fs.mkdir(path.dirname(defaultEnvJsonPath), { recursive: true })
247
240
  await fs.writeFile(defaultEnvJsonPath, JSON.stringify(defaultEnvJson, null, 2));
248
241
  }
249
242
  }
@@ -251,13 +244,15 @@ class HanaDeployer {
251
244
 
252
245
  _getVCAPServicesEntry(serviceInstanceName, serviceKey) {
253
246
  return {
254
- hana: [
255
- {
256
- name: serviceInstanceName,
257
- tags: ['hana'],
258
- credentials: serviceKey
259
- }
260
- ]
247
+ VCAP_SERVICES: {
248
+ hana: [
249
+ {
250
+ name: serviceInstanceName,
251
+ tags: ['hana'],
252
+ credentials: serviceKey
253
+ }
254
+ ]
255
+ }
261
256
  };
262
257
  }
263
258
 
@@ -20,7 +20,10 @@ class HdiDeployUtil {
20
20
  await this._executeDeploy(dbDir, env, logger);
21
21
  }
22
22
 
23
- async deploy(dbDir, vcapServices, options = {}) {
23
+ async deploy(dbDir, vcapEnv, options) {
24
+ vcapEnv = vcapEnv || {}; // handles null and undefined
25
+ options = options || {};
26
+
24
27
  LOG.log();
25
28
  LOG.log(`Deploying to HANA from ${dbDir}`);
26
29
 
@@ -34,7 +37,15 @@ class HdiDeployUtil {
34
37
  deployerEnv = hdiDeployLib.clean_env(deployerEnv);
35
38
  }
36
39
 
37
- deployerEnv.VCAP_SERVICES = JSON.stringify(vcapServices);
40
+ if (vcapEnv.VCAP_SERVICES) {
41
+ deployerEnv.VCAP_SERVICES = JSON.stringify(vcapEnv.VCAP_SERVICES);
42
+ }
43
+ if (vcapEnv.SERVICE_REPLACEMENTS) {
44
+ deployerEnv.SERVICE_REPLACEMENTS = JSON.stringify(vcapEnv.SERVICE_REPLACEMENTS);
45
+ }
46
+ if (vcapEnv.TARGET_CONTAINER) {
47
+ deployerEnv.TARGET_CONTAINER = vcapEnv.TARGET_CONTAINER;
48
+ }
38
49
 
39
50
  if (options.autoUndeploy) {
40
51
  LOG.log(`Hdi deployer automatically undeploys deleted resources using --auto-undeploy.`);
@@ -111,7 +111,7 @@ exports.create = async function (db, csn=db.model, o) {
111
111
  console.log(); for (let each of drops) console.log(each)
112
112
  console.log(); for (let each of creates) console.log(each,'\n')
113
113
  return
114
- } else return db.tx (async tx => {
114
+ } else return db.run (async tx => {
115
115
  await tx.run(drops)
116
116
  await tx.run(creates)
117
117
  return true
@@ -120,7 +120,7 @@ exports.create = async function (db, csn=db.model, o) {
120
120
  }
121
121
 
122
122
 
123
- exports.init = (db, csn=db.model, log=()=>{}) => db.tx (async tx => {
123
+ exports.init = (db, csn=db.model, log=()=>{}) => db.run (async tx => {
124
124
  const resources = await exports.resources(csn, {testdata: cds.env.features.test_data})
125
125
  const inits=[]
126
126
  for (let [file,e] of Object.entries(resources)) {
@@ -126,6 +126,10 @@
126
126
  }
127
127
  }
128
128
  },
129
+ "extends": {
130
+ "description": "Name of the application that shall be extended",
131
+ "type": "string"
132
+ },
129
133
  "requires": {
130
134
  "type": "object",
131
135
  "description": "CDS Dependencies to databases and services can be configured by listing them within a requires section.",
@@ -567,7 +571,7 @@
567
571
  "uniqueItems": true,
568
572
  "items": {
569
573
  "type": "object",
570
- "additionalProperties": true,
574
+ "additionalProperties": false,
571
575
  "properties": {
572
576
  "for": {
573
577
  "type": "array",
@@ -595,6 +599,11 @@
595
599
  "type": "integer",
596
600
  "description": "Number of fields to be added at most",
597
601
  "minimum": 1
602
+ },
603
+ "new-entities": {
604
+ "type": "integer",
605
+ "description": "Number of entities to be added at most",
606
+ "minimum": 1
598
607
  }
599
608
  }
600
609
  }
package/lib/index.js CHANGED
@@ -73,7 +73,7 @@ const cds = module.exports = extend (new facade) .with ({
73
73
  Request: require ('./req/request'),
74
74
  Event: require ('./req/event'),
75
75
  User: require ('./req/user'),
76
- ql: lazy => require ('./ql/cds-ql'),
76
+ ql: require ('./ql/cds-ql'),
77
77
  tx: (..._) => (cds.db || cds.Service.prototype) .tx (..._),
78
78
  /** @type Service */ db: undefined,
79
79
 
@@ -56,6 +56,24 @@ module.exports = (module, level, ...args) => {
56
56
  if (cf.length) toLog['#cf'] = { string: cf }
57
57
  }
58
58
 
59
+ const getCircularReplacer = () => {
60
+ const seen = new WeakSet()
61
+ return (key, value) => {
62
+ if (typeof value === "object" && value !== null) {
63
+ if (seen.has(value)) {
64
+ return 'cyclic'
65
+ }
66
+ seen.add(value)
67
+ }
68
+ return value
69
+ }
70
+ }
71
+
59
72
  // return array with the stringified toLog (to avoid multiple log lines) as the sole element
60
- return [util.inspect(toLog, { compact: true, breakLength: Infinity, depth: null })]
73
+ try {
74
+ return [JSON.stringify(toLog)]
75
+ } catch (e) {
76
+ // try again with removed circular references
77
+ return [JSON.stringify(toLog, getCircularReplacer())]
78
+ }
61
79
  }
package/lib/ql/Query.js CHANGED
@@ -1,7 +1,8 @@
1
+ const { AsyncResource } = require('async_hooks')
1
2
  const { inspect } = require('util')
2
3
  const cds = require('../index')
3
4
 
4
- module.exports = class Query {
5
+ class Query {
5
6
 
6
7
  constructor(_={}) { this[this.cmd] = _ }
7
8
 
@@ -16,8 +17,10 @@ module.exports = class Query {
16
17
  }
17
18
 
18
19
  /** Turns all queries into Thenables which execute with primary db by default */
19
- then (r,e) {
20
- return (this._srv || cds.db) .run (this) .then (r,e)
20
+ get then() {
21
+ const srv = this._srv || cds.db || cds.error `Can't execute query as no primary database is connected.`
22
+ const q = new AsyncResource('await cds.query')
23
+ return (r,e) => q.runInAsyncScope (srv.run, srv, this) .then (r,e)
21
24
  }
22
25
 
23
26
  /** Beautifies output in REPL */
@@ -87,3 +90,6 @@ const _target4 = (target, arg2) => target && (
87
90
  )
88
91
 
89
92
  const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
93
+
94
+
95
+ module.exports = Query
package/lib/ql/SELECT.js CHANGED
@@ -94,7 +94,7 @@ module.exports = class SELECT extends Whereable {
94
94
  return Object.defineProperty(this, '_where_or_having', { value: 'on', configurable: true })
95
95
  }
96
96
  on (...args) {
97
- if (!this.SELECT.from || !this.SELECT.from.join)
97
+ if (!this.SELECT.from || !this.SELECT.from.join || !this.SELECT.from.args) // `join` can also be a function, e.g. in SELECT.from(SELECT.from('foo'))
98
98
  throw new Error(`Invalid call of "SELECT.on()" without prior call of "SELECT.join()"`)
99
99
  return this._where (args,'and','on')
100
100
  }
package/lib/ql/UPDATE.js CHANGED
@@ -54,8 +54,8 @@ module.exports = class UPDATE extends Whereable {
54
54
  const o = args[0]
55
55
  for (let col in o) {
56
56
  let op = '=', v = o[col]
57
- if (typeof v === 'object' && !(v === null || v.map || v.pipe || v instanceof Buffer || v instanceof Date)) {
58
- let o = Object.keys(v)[0] || this._expected `${{v}} to be an object with an operator as single key`
57
+ if (typeof v === 'object' && !(v === null || v.map || v.pipe || v instanceof Buffer || v instanceof Date || v instanceof RegExp)) {
58
+ let o = Object.keys(v)[0] //|| this._expected `${{v}} to be an object with an operator as single key`
59
59
  if (o in operators) v = v[op=o]
60
60
  }
61
61
  _add (this, col, op, v && (v.val !== undefined || v.ref || v.xpr || v.func || v.SELECT) ? v : {val:v})
package/lib/ql/cds-ql.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const cds = require('../index')
2
+ const Query = require('./Query')
2
3
  require = path => { // eslint-disable-line no-global-assign
3
4
  const clazz = module.require (path); if (!clazz._api) return clazz
4
5
  Object.defineProperty (clazz.prototype, 'cmd', { value: path.match(/\w+$/)[0] })
@@ -6,12 +7,14 @@ require = path => { // eslint-disable-line no-global-assign
6
7
  }
7
8
 
8
9
  module.exports = Object.assign (_deprecated_srv_ql, { cdr: true,
10
+ Query,
9
11
  SELECT: require('./SELECT'),
10
12
  INSERT: require('./INSERT'),
11
13
  UPDATE: require('./UPDATE'),
12
14
  DELETE: require('./DELETE'),
13
15
  CREATE: require('./CREATE'),
14
16
  DROP: require('./DROP'),
17
+ clone(q) { return Query.prototype.clone.call(q) }
15
18
  })
16
19
 
17
20
  function _deprecated_srv_ql() { // eslint-disable-next-line no-console
@@ -22,18 +25,9 @@ function _deprecated_srv_ql() { // eslint-disable-next-line no-console
22
25
  return module.exports
23
26
  }
24
27
 
25
- if (cds.env.features.cls && cds.env.features.debug_queries) {
26
- const Query = require('./Query'), { then } = Query.prototype
27
- const { AsyncResource } = require('async_hooks')
28
- Object.defineProperty (Query.prototype,'then',{ get(){
29
- const q = new AsyncResource('cds.Query')
30
- return (r,e) => q.runInAsyncScope (then,this,r,e)
31
- }})
32
- }
33
-
34
28
  module.exports._reset = ()=>{ // for strange tests only
35
29
  const _name = cds.env.sql.names === 'quoted' ? n =>`"${n}"` : n => n.replace(/[.:]/g,'_')
36
- Object.defineProperty (require('./Query').prototype,'valueOf',{ configurable:1, value: function(cmd=this.cmd) {
30
+ Object.defineProperty (Query.prototype,'valueOf',{ configurable:1, value: function(cmd=this.cmd) {
37
31
  return `${cmd} ${_name(this._target.name)} `
38
32
  }})
39
33
  return this