@sap/cds 8.5.0 → 8.6.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 +54 -3
- package/_i18n/i18n.properties +4 -7
- package/eslint.config.mjs +1 -1
- package/lib/compile/etc/properties.js +2 -2
- package/lib/compile/for/java.js +15 -3
- package/lib/compile/for/lean_drafts.js +44 -34
- package/lib/compile/for/nodejs.js +19 -10
- package/lib/compile/minify.js +2 -4
- package/lib/compile/parse.js +106 -72
- package/lib/compile/to/edm.js +19 -9
- package/lib/compile/to/hana.js +25 -21
- package/lib/compile/to/sql.js +15 -8
- package/lib/core/linked-csn.js +10 -4
- package/lib/dbs/cds-deploy.js +2 -2
- package/lib/env/cds-env.js +76 -66
- package/lib/env/defaults.js +1 -0
- package/lib/i18n/bundles.js +2 -1
- package/lib/i18n/localize.js +2 -2
- package/lib/index.js +24 -18
- package/lib/ql/CREATE.js +11 -6
- package/lib/ql/DELETE.js +12 -9
- package/lib/ql/DROP.js +15 -8
- package/lib/ql/INSERT.js +19 -14
- package/lib/ql/SELECT.js +95 -168
- package/lib/ql/UPDATE.js +23 -14
- package/lib/ql/UPSERT.js +15 -2
- package/lib/ql/Whereable.js +44 -118
- package/lib/ql/cds-ql.js +222 -28
- package/lib/ql/{Query.js → cds.ql-Query.js} +52 -41
- package/lib/ql/cds.ql-predicates.js +133 -0
- package/lib/ql/cds.ql-projections.js +111 -0
- package/lib/ql/cqn.d.ts +146 -0
- package/lib/srv/cds-connect.js +3 -3
- package/lib/srv/cds-serve.js +2 -2
- package/lib/srv/cds.Service.js +132 -0
- package/lib/srv/{srv-api.js → cds.ServiceClient.js} +16 -71
- package/lib/srv/cds.ServiceProvider.js +20 -0
- package/lib/srv/factory.js +20 -8
- package/lib/srv/protocols/hcql.js +2 -3
- package/lib/srv/protocols/index.js +3 -3
- package/lib/srv/srv-dispatch.js +7 -6
- package/lib/srv/srv-handlers.js +103 -113
- package/lib/srv/srv-methods.js +14 -14
- package/lib/srv/srv-tx.js +5 -3
- package/lib/utils/cds-utils.js +2 -2
- package/libx/_runtime/cds-services/adapter/odata-v4/handlers/error.js +3 -3
- package/libx/_runtime/cds.js +2 -1
- package/libx/_runtime/common/aspects/service.js +25 -0
- package/libx/_runtime/common/generic/auth/index.js +5 -0
- package/libx/_runtime/common/generic/auth/restrict.js +36 -14
- package/libx/_runtime/common/generic/auth/service.js +24 -0
- package/libx/_runtime/common/generic/auth/utils.js +14 -6
- package/libx/_runtime/common/generic/etag.js +1 -1
- package/libx/_runtime/common/utils/cqn.js +1 -2
- package/libx/_runtime/common/utils/cqn2cqn4sql.js +1 -1
- package/libx/_runtime/common/utils/generateOnCond.js +7 -3
- package/libx/_runtime/common/utils/postProcess.js +4 -1
- package/libx/_runtime/common/utils/restrictions.js +1 -0
- package/libx/_runtime/fiori/lean-draft.js +53 -42
- package/libx/_runtime/messaging/enterprise-messaging-utils/registerEndpoints.js +1 -1
- package/libx/_runtime/remote/Service.js +2 -0
- package/libx/_runtime/remote/utils/client.js +12 -0
- package/libx/odata/ODataAdapter.js +2 -1
- package/libx/odata/index.js +5 -3
- package/libx/odata/middleware/batch.js +4 -0
- package/libx/odata/middleware/create.js +2 -2
- package/libx/odata/middleware/delete.js +2 -2
- package/libx/odata/middleware/operation.js +2 -2
- package/libx/odata/middleware/read.js +14 -12
- package/libx/odata/middleware/service-document.js +16 -8
- package/libx/odata/middleware/update.js +2 -2
- package/libx/odata/parse/afterburner.js +64 -30
- package/libx/odata/parse/grammar.peggy +95 -0
- package/libx/odata/parse/parser.js +1 -1
- package/libx/odata/utils/index.js +6 -1
- package/libx/odata/utils/metadata.js +69 -75
- package/libx/odata/utils/postProcess.js +24 -3
- package/package.json +1 -1
- package/server.js +1 -1
- package/lib/ql/parse.js +0 -36
- /package/lib/ql/{infer.js → cds.ql-infer.js} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,8 +1,59 @@
|
|
|
1
1
|
# Change Log
|
|
2
2
|
|
|
3
3
|
- All notable changes to this project are documented in this file.
|
|
4
|
-
- The format is based on [Keep a Changelog](
|
|
5
|
-
- This project adheres to [Semantic Versioning](
|
|
4
|
+
- The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
5
|
+
- This project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## Version 8.6.0 - 2024-12-17
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- `SELECT.from` now supports full-query tagged template literals, e.g.: ``` SELECT.from`Books where ID=${201}` ```
|
|
12
|
+
- `cds.ql` enhanced by functions to facilitate construction of CQN objects.
|
|
13
|
+
- `cds.ql` became a function to turn CQN objects, CQL strings, or tagged template literals into instances of the respective `cds.ql` class.
|
|
14
|
+
- New `cds` events to allow multitenant plugins: `compile.for.runtime`, `compile.to.dbx`, `compile.to.edmx`.
|
|
15
|
+
- `cds.env` now supports `.cdsrc.js` and `.cdsrc.yaml` files, also in plugins.
|
|
16
|
+
- `cds.env` now supports profile-specific `.env` files, e.g. `.hybrid.env` or `.attic.env`.
|
|
17
|
+
- Experimental OData parsing for hierarchy requests (`descendants`, `ancestors`, `TopLevels`)
|
|
18
|
+
- The new OData adapter now supports `cds.odata.containment`. Contained entities can only be accessed via their parents and do not show up as EntitySets in $metadata and the service document.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- `CDL`, `CQL`, and `CXL` globals are deprecated => use respective functions from `cds.ql` instead.
|
|
23
|
+
- `CREATE`, and `DROP` globals are deprecated => use respective functions from `cds.ql` instead.
|
|
24
|
+
- Zulu time zone information is stripped from `cds.DateTime` properties when querying Odata V2 remote services
|
|
25
|
+
- Processing of `@restrict.where` was aligned with CAP Java:
|
|
26
|
+
+ Instance-based authorization on app service calls does not consider custom `WHERE` clauses of `UPDATE`/`DELETE` queries
|
|
27
|
+
+ Until `@sap/cds^9`, this change can be deactivated via `cds.env.features.compat_restrict_where = true`
|
|
28
|
+
+ Simple static clauses (e.g., `$user.level > 5`) are no longer evaluated by the server but added to the respective SQL regardless. As a result, requests may receive a response of `2xx` with an empty body instead of a `403`.
|
|
29
|
+
+ Until `@sap/cds^9`, this change can be deactivated via `cds.env.features.compat_static_auth = true`
|
|
30
|
+
+ Read restrictions on the entity are no longer taken into consideration when evaluating restrictions on bound actions/ functions
|
|
31
|
+
+ Until `@sap/cds^9`, this change can be deactivated via `cds.env.features.compat_restrict_bound = true`
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- ETag calculation if column was provided as Javascript Date
|
|
36
|
+
- Forwarding of `/$count` queries while mocking the external service
|
|
37
|
+
- Resolving of implicit function parameters (e.g `GET .../test.foo?x='bar'`)
|
|
38
|
+
- Arrayed elements are not part of response unless explicitly selected with `$select`
|
|
39
|
+
- In case of nonexistent user attributes (`$user.X`), only the subclause gets substituted with `false`
|
|
40
|
+
- `@odata.context` in new OData adapter:
|
|
41
|
+
+ Fixed crash for requests to actions/functions when `cds.env.odata.context_with_columns` is enabled
|
|
42
|
+
+ Aggregation functions with `$apply` are now returned when `cds.env.odata.context_with_columns` is enabled
|
|
43
|
+
+ `@odata.context` is now the first property in the response values of `concat` requests
|
|
44
|
+
+ Binary key values are now properly encoded and formatted
|
|
45
|
+
+ Fixed keys appearing as `(undefined)` for updates via navigation to-one
|
|
46
|
+
+ Fixed key value pairs being returned as `undefined=undefined` for property access of aspects
|
|
47
|
+
+ Backlinks no longer appear as keys for property access of aspects
|
|
48
|
+
+ Non-anonymous structured types are now prefixed with the service name
|
|
49
|
+
+ Structured types no longer end with `/$entity`
|
|
50
|
+
|
|
51
|
+
## Version 8.5.1 - 2024-12-06
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- `cds deploy --dry --model-only` no longer tries to load a SQLite database
|
|
56
|
+
- Requests with HTTP methods other than `POST` to the `/$batch` endpoint are now rejected when using the new OData adapter
|
|
6
57
|
|
|
7
58
|
## Version 8.5.0 - 2024-11-25
|
|
8
59
|
|
|
@@ -515,7 +566,7 @@
|
|
|
515
566
|
- Support for `@odata.draft.bypass` to allow direct modifications of active instances.
|
|
516
567
|
- `req.user.tokenInfo` for `@sap/xssec`-based authentication (`ias`, `jwt`, `xsuaa`)
|
|
517
568
|
- `cds.fiori.draft_lock_timeout` as successor of `cds.drafts.cancellationTimeout`.
|
|
518
|
-
+ Possible values are
|
|
569
|
+
+ Possible values are `/^([0-9]+)(h|hrs|min)$/` or a number in milliseconds.
|
|
519
570
|
- There is a new `sap.common.Timezones` entity with a basic time zone definition. There will be accompanying data in package `@sap/cds-common-content`.
|
|
520
571
|
- Deprecation warnings for configuration options `cds.drafts.cancellationTimeout`, `cds.features.serve_on_root`, `cds.features.stream_compat`, `cds.fiori.lean_draft` and `cds.requires.middlewares`, as well as for the properties `req.user.locale` and `req.user.tenant`. The deprecation warnings can be turned off by setting `cds.features.deprecated` to `off`.
|
|
521
572
|
|
package/_i18n/i18n.properties
CHANGED
|
@@ -44,7 +44,7 @@ Currency=Currency
|
|
|
44
44
|
CurrencyCode=Currency Code
|
|
45
45
|
|
|
46
46
|
#XTIT: Currency Code Description
|
|
47
|
-
CurrencyCode.Description=
|
|
47
|
+
CurrencyCode.Description=Currency code as specified by ISO 4217
|
|
48
48
|
|
|
49
49
|
#XTIT: Currency Symbol
|
|
50
50
|
CurrencySymbol=Currency Symbol
|
|
@@ -59,7 +59,7 @@ Country=Country/Region
|
|
|
59
59
|
CountryCode=Country/Region Code
|
|
60
60
|
|
|
61
61
|
#XTIT: Country/Region Code Description
|
|
62
|
-
CountryCode.Description=
|
|
62
|
+
CountryCode.Description=Country/region code as specified by ISO 3166-1
|
|
63
63
|
|
|
64
64
|
#XTIT: Language
|
|
65
65
|
Language=Language
|
|
@@ -68,10 +68,7 @@ Language=Language
|
|
|
68
68
|
LanguageCode=Language Code
|
|
69
69
|
|
|
70
70
|
#XTIT: Language Code Description
|
|
71
|
-
LanguageCode.Description=
|
|
72
|
-
|
|
73
|
-
#XTIT Time zone code
|
|
74
|
-
TimeZoneCode=Time Zone Code
|
|
71
|
+
LanguageCode.Description=Language code as specified by ISO 639-1
|
|
75
72
|
|
|
76
73
|
#XTIT: User Identifier
|
|
77
74
|
UserID=User ID
|
|
@@ -83,7 +80,7 @@ Name=Name
|
|
|
83
80
|
Description=Description
|
|
84
81
|
|
|
85
82
|
#XTOL: A user's unique Indentifier
|
|
86
|
-
UserID.Description=
|
|
83
|
+
UserID.Description=User's unique ID
|
|
87
84
|
|
|
88
85
|
#XTIT: Admin data for a draft document
|
|
89
86
|
Draft_DraftAdministrativeData=Draft Administrative Data
|
package/eslint.config.mjs
CHANGED
|
@@ -9,14 +9,14 @@ function read (res, ext = '.properties') {
|
|
|
9
9
|
// unicode chars in them (e.g.German umlauts), so let's parse in utf-8.
|
|
10
10
|
if (!/\.(env|.+rc)$/.test(res) && !path.extname(res)) res += ext
|
|
11
11
|
const src = fs.readFileSync(path.resolve(res),'utf-8')
|
|
12
|
-
|
|
13
|
-
return Object.defineProperty (properties, '_source', {value:res})
|
|
12
|
+
return Object.defineProperty (Properties.parse(src), '_source', {value:res})
|
|
14
13
|
} catch (e) {
|
|
15
14
|
if (e.code !== 'ENOENT') throw new Error (`Corrupt ${ext} file: ${res+ext}`)
|
|
16
15
|
}
|
|
17
16
|
}
|
|
18
17
|
|
|
19
18
|
function parse (props) {
|
|
19
|
+
if (props.match(/^\s*{/)) return JSON.parse(props)
|
|
20
20
|
const lines = props.split(/(?<![\\\r])\r?\n/)
|
|
21
21
|
const rows = lines.filter(each => !!each.trim()).map(each => each.replace(/\\\r?\n/, '')).map(each => {
|
|
22
22
|
const index = each.indexOf('=')
|
package/lib/compile/for/java.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The only entry point to produce CSNs consumed by the Java Runtime
|
|
2
2
|
|
|
3
3
|
const cds = require ('../../index')
|
|
4
|
+
const TRACE = cds.debug('trace')
|
|
4
5
|
|
|
5
6
|
function _4java (csn,o) {
|
|
6
7
|
const compile = require ('../cdsc');
|
|
@@ -18,9 +19,7 @@ function _4java_tmp (csn,o) { // as long as compile.for.java is not definitely t
|
|
|
18
19
|
return _4draft (dsn, o);
|
|
19
20
|
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
if ('_4java' in csn) return csn._4java
|
|
23
|
-
// cds.minify (csn) ?
|
|
22
|
+
function _compile_for_java (csn,o) {
|
|
24
23
|
const dsn = (!cds.env.features._ucsn_) ? cds.compile.for.odata (csn,o||{}) : _4java (csn,o||{});
|
|
25
24
|
if (dsn.definitions) for (let [name,d] of Object.entries(dsn.definitions)) {
|
|
26
25
|
// Add @cds.external to external services
|
|
@@ -35,3 +34,16 @@ module.exports = function cds_compile_for_java (csn,o) {
|
|
|
35
34
|
Object.defineProperty (dsn, '_4java', {value:dsn})
|
|
36
35
|
return dsn
|
|
37
36
|
}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
module.exports = function cds_compile_for_java (csn,o) {
|
|
40
|
+
if ('_4java' in csn) return csn._4java
|
|
41
|
+
TRACE?.time('cds.compile 4java'.padEnd(22))
|
|
42
|
+
try {
|
|
43
|
+
// csn = cds.minify (csn)
|
|
44
|
+
let result, next = ()=> result ??= _compile_for_java (csn,o)
|
|
45
|
+
cds.emit ('compile.for.runtime', csn, o, next)
|
|
46
|
+
return next() //> in case no handler called next
|
|
47
|
+
}
|
|
48
|
+
finally { TRACE?.timeEnd('cds.compile 4java'.padEnd(22)) }
|
|
49
|
+
}
|
|
@@ -19,6 +19,48 @@ function _isCompositionBacklink(e) {
|
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
|
|
23
|
+
// NOTE: Keep outside of the function to avoid calling the parser repeatedly
|
|
24
|
+
const { Draft } = cds.linked(`
|
|
25
|
+
entity ActiveEntity { key ID: UUID; }
|
|
26
|
+
entity Draft {
|
|
27
|
+
virtual IsActiveEntity : Boolean; // REVISIT: these are calculated fields, aren't they?
|
|
28
|
+
virtual HasDraftEntity : Boolean; // REVISIT: these are calculated fields, aren't they?
|
|
29
|
+
HasActiveEntity : Boolean; // This should be written !!!
|
|
30
|
+
DraftAdministrativeData : Association to DRAFT.DraftAdministrativeData;
|
|
31
|
+
DraftAdministrativeData_DraftUUID : UUID;
|
|
32
|
+
// SiblingEntity : Association to ActiveEntity; // REVISIT: Why didn't we use a managed assoc here?
|
|
33
|
+
}
|
|
34
|
+
entity DRAFT.DraftAdministrativeData {
|
|
35
|
+
key DraftUUID : UUID;
|
|
36
|
+
LastChangedByUser : String(256); LastChangeDateTime : Timestamp;
|
|
37
|
+
CreatedByUser : String(256); CreationDateTime : Timestamp;
|
|
38
|
+
InProcessByUser : String(256);
|
|
39
|
+
DraftIsCreatedByMe : Boolean; // REVISIT: these are calculated fields, aren't they?
|
|
40
|
+
DraftIsProcessedByMe : Boolean; // REVISIT: these are calculated fields, aren't they?
|
|
41
|
+
}
|
|
42
|
+
`).definitions
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
function DraftEntity4 (active, name = active.name+'.drafts') {
|
|
46
|
+
|
|
47
|
+
const draft = Object.create (active, {
|
|
48
|
+
name: { value: name }, // REVISIT: lots of things break if we do that!
|
|
49
|
+
elements: { value: { ...active.elements, ...Draft.elements }, enumerable: true },
|
|
50
|
+
actives: { value: active },
|
|
51
|
+
query: { value: undefined }, // to not inherit that from active
|
|
52
|
+
// drafts: { value: undefined }, // to not inherit that from active -> doesn't work yet as the coding in lean-draft.js uses .drafts to identify both active and draft entities
|
|
53
|
+
isDraft: { value: true },
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
// for quoted names, we need to overwrite the cds.persistence.name of the derived, draft entity
|
|
57
|
+
const _pname = active['@cds.persistence.name']
|
|
58
|
+
if (_pname) draft['@cds.persistence.name'] = _pname + '_drafts'
|
|
59
|
+
|
|
60
|
+
return draft
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
|
|
22
64
|
module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
23
65
|
function _redirect(assoc, target) {
|
|
24
66
|
assoc.target = target.name
|
|
@@ -26,54 +68,22 @@ module.exports = function cds_compile_for_lean_drafts(csn) {
|
|
|
26
68
|
}
|
|
27
69
|
|
|
28
70
|
function _isDraft(def) {
|
|
71
|
+
// return 'DraftAdministrativeData' in def.elements
|
|
29
72
|
return (
|
|
30
73
|
def.associations?.DraftAdministrativeData ||
|
|
31
74
|
(def.own('@odata.draft.enabled') && def.own('@Common.DraftRoot.ActivationAction'))
|
|
32
75
|
)
|
|
33
76
|
}
|
|
34
77
|
|
|
35
|
-
const { Draft } = cds.linked(`
|
|
36
|
-
entity ActiveEntity { key ID: UUID; }
|
|
37
|
-
entity Draft {
|
|
38
|
-
virtual IsActiveEntity : Boolean; // REVISIT: these are calculated fields, aren't they?
|
|
39
|
-
virtual HasDraftEntity : Boolean; // REVISIT: these are calculated fields, aren't they?
|
|
40
|
-
HasActiveEntity : Boolean; // This should be written !!!
|
|
41
|
-
DraftAdministrativeData : Association to DRAFT.DraftAdministrativeData;
|
|
42
|
-
DraftAdministrativeData_DraftUUID : UUID;
|
|
43
|
-
// SiblingEntity : Association to ActiveEntity; // REVISIT: Why didn't we use a managed assoc here?
|
|
44
|
-
}
|
|
45
|
-
entity DRAFT.DraftAdministrativeData {
|
|
46
|
-
key DraftUUID : UUID;
|
|
47
|
-
LastChangedByUser : String(256); LastChangeDateTime : Timestamp;
|
|
48
|
-
CreatedByUser : String(256); CreationDateTime : Timestamp;
|
|
49
|
-
InProcessByUser : String(256);
|
|
50
|
-
DraftIsCreatedByMe : Boolean; // REVISIT: these are calculated fields, aren't they?
|
|
51
|
-
DraftIsProcessedByMe : Boolean; // REVISIT: these are calculated fields, aren't they?
|
|
52
|
-
}
|
|
53
|
-
`).definitions
|
|
54
|
-
|
|
55
78
|
function addDraftEntity(active, model) {
|
|
56
79
|
const _draftEntity = active.name + '.drafts'
|
|
57
80
|
const d = model.definitions[_draftEntity]
|
|
58
81
|
if (d) return d
|
|
59
82
|
// We need to construct a fake draft entity definition
|
|
60
83
|
// We cannot use new cds.entity because runtime aspects would be missing
|
|
61
|
-
const draft =
|
|
62
|
-
__proto__: active,
|
|
63
|
-
name: _draftEntity,
|
|
64
|
-
elements: { ...active.elements, ...Draft.elements },
|
|
65
|
-
query: undefined
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// for quoted names, we need to overwrite the cds.persistence.name of the derived, draft entity
|
|
69
|
-
if (active['@cds.persistence.name'])
|
|
70
|
-
draft['@cds.persistence.name'] = active['@cds.persistence.name'] + '_drafts'
|
|
71
|
-
|
|
84
|
+
const draft = new DraftEntity4 (active, _draftEntity)
|
|
72
85
|
Object.defineProperty(model.definitions, _draftEntity, { value: draft })
|
|
73
86
|
Object.defineProperty(active, 'drafts', { value: draft })
|
|
74
|
-
Object.defineProperty(active, 'actives', { value: active })
|
|
75
|
-
Object.defineProperty(draft, 'actives', { value: active })
|
|
76
|
-
Object.defineProperty(draft, 'isDraft', { value: true })
|
|
77
87
|
|
|
78
88
|
// Positive list would be bigger (search, requires, fiori, ...)
|
|
79
89
|
if (draft['@readonly']) draft['@readonly'] = undefined
|
|
@@ -2,16 +2,25 @@ const { unfold_csn } = require ('../etc/_localized')
|
|
|
2
2
|
const cds = require ('../../index')
|
|
3
3
|
const TRACE = cds.debug('trace')
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let dsn =
|
|
9
|
-
dsn =
|
|
10
|
-
dsn =
|
|
11
|
-
dsn = cds.linked (dsn)
|
|
5
|
+
|
|
6
|
+
function _compile_for_nodejs (csn, o) {
|
|
7
|
+
let min = cds.minify (csn)
|
|
8
|
+
let dsn = cds.compile.for.odata(min, o) //> creates a partial copy -> avoid any cds.linked() before
|
|
9
|
+
dsn = unfold_csn(dsn)
|
|
10
|
+
dsn = cds.linked(dsn)
|
|
12
11
|
cds.compile.for.lean_drafts(dsn, o)
|
|
13
|
-
Object.defineProperty
|
|
14
|
-
Object.defineProperty
|
|
15
|
-
TRACE?.timeEnd('cds.compile 4nodejs'.padEnd(22))
|
|
12
|
+
Object.defineProperty(csn, '_4nodejs', { value: dsn })
|
|
13
|
+
Object.defineProperty(dsn, '_4nodejs', { value: dsn })
|
|
16
14
|
return dsn
|
|
17
15
|
}
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
module.exports = function cds_compile_for_nodejs (csn,o) {
|
|
19
|
+
if ('_4nodejs' in csn) return csn._4nodejs
|
|
20
|
+
TRACE?.time('cds.compile 4nodejs'.padEnd(22)); try {
|
|
21
|
+
let result, next = ()=> result ??= _compile_for_nodejs (csn,o)
|
|
22
|
+
cds.emit ('compile.for.runtime', csn, o, next)
|
|
23
|
+
return next() //> in case no handler called next
|
|
24
|
+
}
|
|
25
|
+
finally { TRACE?.timeEnd('cds.compile 4nodejs'.padEnd(22)) }
|
|
26
|
+
}
|
package/lib/compile/minify.js
CHANGED
|
@@ -3,7 +3,7 @@ const DEBUG = cds.debug('minify')
|
|
|
3
3
|
|
|
4
4
|
module.exports = function cds_minify (csn, roots = cds.env.features.skip_unused) {
|
|
5
5
|
if (roots === false) return csn
|
|
6
|
-
if (csn
|
|
6
|
+
if ((csn.meta??={}).minified) return csn
|
|
7
7
|
const all = csn.definitions, reached = new Set
|
|
8
8
|
if (roots === 'services') {
|
|
9
9
|
for (let n in all) if (all[n].kind === 'service') _visit_service(n)
|
|
@@ -58,8 +58,6 @@ module.exports = function cds_minify (csn, roots = cds.env.features.skip_unused)
|
|
|
58
58
|
const minified = csn, less = minified.definitions = {}
|
|
59
59
|
for (let n in all) if (reached.has(all[n])) less[n] = all[n]
|
|
60
60
|
else DEBUG?.('skipping', all[n].kind, n)
|
|
61
|
-
|
|
61
|
+
;(minified.meta??={}).minified = true
|
|
62
62
|
return minified
|
|
63
63
|
}
|
|
64
|
-
|
|
65
|
-
const _minified = Symbol('minified')
|
package/lib/compile/parse.js
CHANGED
|
@@ -1,86 +1,120 @@
|
|
|
1
1
|
const cdsc = require ('@sap/cds-compiler')
|
|
2
2
|
const cds = require ('../index')
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
const cds_parse = (src,o) => cds.compile (src,o,'parsed')
|
|
6
|
-
const parse = module.exports = Object.assign (cds_parse, {
|
|
4
|
+
const parse = module.exports = exports = (...args) => parse.cdl (...args)
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
exports.cdl = function cdl (x,...etc) {
|
|
7
|
+
if (x.raw) return parse.ttl (cdl, x,...etc)
|
|
8
|
+
else return cds.compile (x, etc[0], 'parsed')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
exports.cql = function cql (x,...etc) { try {
|
|
12
|
+
if (x.raw) return parse.ttl (cql, x,...etc)
|
|
13
|
+
// add missing 'from' clause if not present -> REVISIT: this is a hack unless compiler accepts partial cql
|
|
14
|
+
const from = x.match(/ from /i); if (!from) x = x.replace(/( where | having | group by | order by | limit |$)/i, x => ' from $' + x)
|
|
15
|
+
const cqn = cdsc.parse.cql(x,undefined,{ messages:[], ...etc[0] })
|
|
16
|
+
if (!from) delete cqn.SELECT.from
|
|
17
|
+
return cqn
|
|
18
|
+
} catch(e) {
|
|
19
|
+
// cds-compiler v5 does not put messages into `e.message` anymore; render them explicitly
|
|
20
|
+
e.message = !e.messages ? e.message : e.toString();
|
|
21
|
+
e.message = e.message.replace('<query>.cds:',`In '${e.cql = x}' at `)
|
|
22
|
+
throw e // with improved error message
|
|
23
|
+
}}
|
|
11
24
|
|
|
12
|
-
|
|
13
|
-
|
|
25
|
+
exports.path = function path (x,...etc) {
|
|
26
|
+
if (x.raw) return parse.ttl (path, x,...etc)
|
|
27
|
+
if (cds.model?.definitions[x]) return {ref:[x]}
|
|
28
|
+
if (/^([\w_.$]+)$/.test(x)) return {ref:[x]} // optimized parsing of simple paths of length 1
|
|
29
|
+
const [,head,tail] = /^([\w._]+)(?::(\w+))?$/.exec(x)||[]
|
|
30
|
+
if (tail) return {ref:[head,...tail.split('.')]}
|
|
31
|
+
if (head) return {ref:[head]}
|
|
32
|
+
const {SELECT} = cdsc.parse.cql('SELECT from '+x)
|
|
33
|
+
return SELECT.from
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
exports.expr = function expr (x,...etc) {
|
|
37
|
+
if (x.raw) return parse.ttl (expr, x,...etc)
|
|
38
|
+
if (typeof x !== 'string') throw cds.error.expected `${{x}} to be an expression string`
|
|
39
|
+
if (x in globals) return globals[x] // optimized parsing of true, false, null
|
|
40
|
+
if (/^([\d.]+)$/.test(x)) return {val:Number(x)} // optimized parsing of numeric vals
|
|
41
|
+
if (/^([\w_$]+)$/.test(x)) return {ref:[x]} // optimized parsing of simple refs of length 1
|
|
42
|
+
if (/^([\w_.$]+)$/.test(x)) return {ref:x.split('.')} // optimized parsing of simple refs of length > 1
|
|
43
|
+
try { return cdsc.parse.expr(x,undefined,{ messages:[], ...etc[0] }) } catch(e) {
|
|
14
44
|
// cds-compiler v5 does not put messages into `e.message` anymore; render them explicitly
|
|
15
45
|
e.message = !e.messages ? e.message : e.toString();
|
|
16
|
-
e.message = e.message.replace('<
|
|
46
|
+
e.message = e.message.replace('<expr>.cds:1:',`In '${e.expr = x}' at `)
|
|
17
47
|
throw e // with improved error message
|
|
18
|
-
}},
|
|
19
|
-
path: (x,...values) => {
|
|
20
|
-
if (x && x.raw) return tagged (parse.path,x,...values)
|
|
21
|
-
if (/^[A-Za-z_0-9.$]*$/.test(x)) return {ref:[x]}
|
|
22
|
-
if ((cds.context?.model || cds.model)?.definitions[x]) return {ref:[x]}
|
|
23
|
-
let {SELECT} = parse.cql('SELECT from '+x)
|
|
24
|
-
return SELECT.from
|
|
25
|
-
},
|
|
26
|
-
column: x => {
|
|
27
|
-
let as = /\s+as\s+(\w+)$/i.exec(x)
|
|
28
|
-
if (as) {
|
|
29
|
-
let col = parse.expr(x.slice(0,as.index)); col.as = as[1]
|
|
30
|
-
return col
|
|
31
|
-
}
|
|
32
|
-
else return parse.expr(x)
|
|
33
|
-
},
|
|
34
|
-
expr: (x,o) => {
|
|
35
|
-
if (typeof x !== 'string') throw cds.error.expected `${{x}} to be an expression string`
|
|
36
|
-
if (x in keywords) return {ref:[x]}
|
|
37
|
-
try { return cdsc.parse.expr(x,undefined,{ messages:[], ...o }) } catch(e) {
|
|
38
|
-
// cds-compiler v5 does not put messages into `e.message` anymore; render them explicitly
|
|
39
|
-
e.message = !e.messages ? e.message : e.toString();
|
|
40
|
-
e.message = e.message.replace('<expr>.cds:1:',`In '${e.expr = x}' at `)
|
|
41
|
-
throw e // with improved error message
|
|
42
|
-
}
|
|
43
|
-
},
|
|
44
|
-
xpr: x => { const y = parse.expr(x); return y.xpr || [y] },
|
|
45
|
-
ref: x => parse.expr(x).ref,
|
|
46
|
-
|
|
47
|
-
properties: (...args) => (parse.properties = require('./etc/properties').parse) (...args),
|
|
48
|
-
yaml: (...args) => (parse.yaml = require('./etc/yaml').parse) (...args),
|
|
49
|
-
csv: (...args) => (parse.csv = require('./etc/csv').parse) (...args),
|
|
50
|
-
json: (...args) => JSON.parse (...args),
|
|
51
|
-
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const tagged = (parse, strings, ...values) => {
|
|
56
|
-
if (!strings.raw) return parse (strings, ...values)
|
|
57
|
-
if (!strings.length) return parse (strings[0])
|
|
58
|
-
const all = new Array (strings.length + values.length)
|
|
59
|
-
for (var i=0; i<strings.length-1; ++i) {
|
|
60
|
-
let v = values[i], s = strings[i]
|
|
61
|
-
all[2*i] = s
|
|
62
|
-
all[2*i+1] = v instanceof cds.entity ? v.name : ':'+i
|
|
63
|
-
if (typeof v === 'string' && s.endsWith(' like ') && !v.includes('%')) values[i] = `%${v}%`
|
|
64
|
-
if (Array.isArray(v) && s.toLowerCase().endsWith(' in ')) values[i] = {list: v.map(cxn4)}
|
|
65
48
|
}
|
|
66
|
-
all[2*i] = strings[i]
|
|
67
|
-
return merge (parse(all.join('')), values)
|
|
68
49
|
}
|
|
69
50
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
51
|
+
exports.xpr = (...args) => {
|
|
52
|
+
const y = parse.expr (...args)
|
|
53
|
+
return y.xpr || [y]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
exports.ref = (x,...etc) => {
|
|
57
|
+
const ntf = {null:{val:null},true:{val:true},false:{val:false}}[x]; if (ntf) return ntf
|
|
58
|
+
if (/^[A-Za-z_$][\w.]*$/.test(x)) return { ref: x.split('.') }
|
|
59
|
+
else return parse.expr (x,...etc)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
exports.properties = (...args) => (parse.properties = require('./etc/properties').parse) (...args)
|
|
63
|
+
exports.yaml = (...args) => (parse.yaml = require('./etc/yaml').parse) (...args)
|
|
64
|
+
exports.csv = (...args) => (parse.csv = require('./etc/csv').parse) (...args)
|
|
65
|
+
exports.json = (...args) => JSON.parse (...args)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
exports.ttl = (parse, strings, ...values) => {
|
|
69
|
+
|
|
70
|
+
// Reusing cached results for identical template strings w/o values
|
|
71
|
+
// if (!values.length) {
|
|
72
|
+
// const cache = _parse_ttl.cache ??= new WeakMap
|
|
73
|
+
// if (cache.has(strings)) return cache.get(strings)
|
|
74
|
+
// let parsed = parse (strings[0])
|
|
75
|
+
// cache.set(strings,parsed)
|
|
76
|
+
// return parsed
|
|
77
|
+
// }
|
|
78
|
+
|
|
79
|
+
let cql = values.reduce ((cql,v,i) => {
|
|
80
|
+
if (Array.isArray(v) && strings[i].match(/ in $/i)) values[i] = { list: v.map(cxn4) }
|
|
81
|
+
return cql + strings[i] + (v instanceof cds.entity ? v.name : ':'+i)
|
|
82
|
+
},'') + strings.at(-1)
|
|
83
|
+
const cqn = parse (cql) //; cqn.$params = values
|
|
84
|
+
return merge (cqn, values)
|
|
85
|
+
|
|
86
|
+
function merge (o,values) {
|
|
87
|
+
for (let k in o) {
|
|
88
|
+
const x = o[k]
|
|
89
|
+
if (!x) continue
|
|
90
|
+
if (x.param) {
|
|
91
|
+
let val = values[x.ref[0]]; if (val === undefined) continue
|
|
92
|
+
let y = o[k] = cxn4(val) //; y.$ = x.ref[0]
|
|
93
|
+
if (x.cast) y.cast = x.cast
|
|
94
|
+
if (x.key) y.key = x.key
|
|
95
|
+
if (x.as) y.as = x.as
|
|
96
|
+
} else if (typeof x === 'object') merge(x,values)
|
|
97
|
+
}
|
|
98
|
+
return o
|
|
81
99
|
}
|
|
82
|
-
return o
|
|
83
100
|
}
|
|
84
101
|
|
|
85
|
-
|
|
86
|
-
|
|
102
|
+
|
|
103
|
+
/** @protected */
|
|
104
|
+
exports._select = (prefix, ttl, suffix) => {
|
|
105
|
+
let [ strings, ...values ] = ttl; strings = [...strings]; strings.raw = strings; // need that as ttl strings are sealed
|
|
106
|
+
if (prefix) strings[0] = `SELECT ${prefix} ${strings[0]}`; else strings[0] = `SELECT ${strings[0]}`
|
|
107
|
+
if (suffix) strings[strings.length-1] += ` ${suffix}`
|
|
108
|
+
return cds.parse.cql (strings, ...values).SELECT
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const is_cqn = x => typeof x === 'object' && (
|
|
112
|
+
'ref' in x ||
|
|
113
|
+
'val' in x ||
|
|
114
|
+
'xpr' in x ||
|
|
115
|
+
'list' in x ||
|
|
116
|
+
'func' in x ||
|
|
117
|
+
'SELECT' in x
|
|
118
|
+
)
|
|
119
|
+
const cxn4 = x => is_cqn(x) ? x : {val:x}
|
|
120
|
+
const globals = { true: {val:true}, false: {val:false}, null: {val:null} }
|
package/lib/compile/to/edm.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cdsc = require ('../cdsc')
|
|
2
2
|
const cds = require ('../../index')
|
|
3
|
+
const TRACE = cds.debug('trace')
|
|
3
4
|
|
|
4
5
|
if (cds.env.features.precompile_edms !== false) {
|
|
5
6
|
const _precompiled = new WeakMap
|
|
@@ -18,21 +19,30 @@ if (cds.env.features.precompile_edms !== false) {
|
|
|
18
19
|
function cds_compile_to_edm (csn,_o) {
|
|
19
20
|
const o = cdsc._options.for.edm(_o) //> used twice below...
|
|
20
21
|
csn = _4odata(csn,o)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
o.
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
TRACE?.time('cds.compile 2edm'.padEnd(22))
|
|
23
|
+
try {
|
|
24
|
+
let result, next = ()=> result = o.service === 'all' ? _many ('.json',
|
|
25
|
+
cdsc.to.edm.all (csn,o), o.as === 'str' ? JSON.stringify : x=>x
|
|
26
|
+
) : cdsc.to.edm (csn,o)
|
|
27
|
+
cds.emit ('compile.to.edmx', csn, o, next) // NOTE: intentionally using same event as for edmx
|
|
28
|
+
return result ??= next() //> in case no handler called next
|
|
29
|
+
}
|
|
30
|
+
finally { TRACE?.timeEnd('cds.compile 2edm'.padEnd(22)) }
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
function cds_compile_to_edmx (csn,_o) {
|
|
30
35
|
const o = cdsc._options.for.edm(_o) //> used twice below...
|
|
31
36
|
csn = _4odata(csn,o)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
37
|
+
TRACE?.time('cds.compile 2edmx'.padEnd(22))
|
|
38
|
+
try {
|
|
39
|
+
let result, next = ()=> result ??= o.service === 'all' ? _many ('.xml',
|
|
40
|
+
cdsc.to.edmx.all (csn,o)
|
|
41
|
+
) : cdsc.to.edmx (csn,o)
|
|
42
|
+
cds.emit ('compile.to.edmx', csn, o, next)
|
|
43
|
+
return next() //> in case no handler called next
|
|
44
|
+
}
|
|
45
|
+
finally { TRACE?.timeEnd('cds.compile 2edmx'.padEnd(22)) }
|
|
36
46
|
}
|
|
37
47
|
|
|
38
48
|
|
package/lib/compile/to/hana.js
CHANGED
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
const cds = require('../..')
|
|
2
2
|
const cdsc = require('../cdsc')
|
|
3
|
+
const TRACE = cds.debug('trace')
|
|
3
4
|
|
|
4
5
|
module.exports = (csn, o, beforeCsn) => {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
6
|
+
TRACE?.time('cds.compile 2hana'.padEnd(22))
|
|
7
|
+
try {
|
|
8
|
+
let result, next = ()=> result ??= function* (){
|
|
9
|
+
csn = cds.minify (csn)
|
|
10
|
+
const { definitions, deletions, migrations, afterImage } = cdsc.to.hdi.migration (csn, o,
|
|
11
|
+
typeof beforeCsn === 'string' ? beforeCsn = JSON.parse(beforeCsn) : beforeCsn
|
|
12
|
+
)
|
|
13
|
+
for (const { name, suffix, sql } of definitions) {
|
|
14
|
+
yield [sql, { file: name + suffix }]
|
|
15
|
+
}
|
|
16
|
+
if (deletions.length > 0) {
|
|
17
|
+
yield [deletions, { file: 'deletions.json' }]
|
|
18
|
+
}
|
|
19
|
+
if (migrations.length > 0) {
|
|
20
|
+
yield [migrations, { file: 'migrations.json' }]
|
|
21
|
+
}
|
|
22
|
+
let needsAfterImage = beforeCsn || Object.values(afterImage.definitions).some(def => def['@cds.persistence.journal'])
|
|
23
|
+
if (needsAfterImage) {
|
|
24
|
+
yield [afterImage, { file: 'afterImage.json' }]
|
|
25
|
+
}
|
|
26
|
+
}()
|
|
27
|
+
cds.emit ('compile.to.dbx', csn, o, next)
|
|
28
|
+
return next() //> in case no handler called next
|
|
11
29
|
}
|
|
12
|
-
|
|
13
|
-
return (function* () {
|
|
14
|
-
for (const { name, suffix, sql } of definitions) {
|
|
15
|
-
yield [sql, { file: name + suffix }]
|
|
16
|
-
}
|
|
17
|
-
if (deletions.length > 0) {
|
|
18
|
-
yield [deletions, { file: 'deletions.json' }]
|
|
19
|
-
}
|
|
20
|
-
if (migrations.length > 0) {
|
|
21
|
-
yield [migrations, { file: 'migrations.json' }]
|
|
22
|
-
}
|
|
23
|
-
if (migrationResult) {
|
|
24
|
-
yield [afterImage, { file: 'afterImage.json' }]
|
|
25
|
-
}
|
|
26
|
-
})()
|
|
30
|
+
finally { TRACE?.timeEnd('cds.compile 2hana'.padEnd(22)) }
|
|
27
31
|
}
|