@sap/cds-dk 10.1.0 → 10.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/bin/bind.js +4 -0
- package/bin/build.js +1 -0
- package/bin/compile/index.js +12 -11
- package/bin/completion/index.js +21 -10
- package/bin/completion/scripts/cds.fish +179 -127
- package/bin/completion/scripts/cds.ps1 +88 -14
- package/bin/completion/scripts/cds.sh +71 -5
- package/bin/completion/scripts/cds.zsh +81 -15
- package/bin/completion/templates/cds-tmpl.ps1 +15 -6
- package/bin/deploy/index.js +7 -3
- package/bin/env.js +4 -1
- package/bin/export.js +9 -1
- package/bin/import.js +4 -1
- package/bin/init.js +1 -0
- package/bin/inspect.js +3 -1
- package/bin/lint.js +5 -3
- package/bin/minify.js +6 -1
- package/bin/repl.js +11 -2
- package/bin/run.js +12 -1
- package/bin/serve.js +13 -2
- package/bin/up.js +4 -1
- package/bin/util/containerize.js +2 -0
- package/bin/watch/index.js +8 -1
- package/lib/build/util.js +2 -4
- package/lib/init/constants.js +1 -1
- package/lib/init/template/data/as-json.js +3 -0
- package/node_modules/@cap-js/db-service/CHANGELOG.md +17 -0
- package/node_modules/@cap-js/db-service/lib/common/generic-pool.js +4 -7
- package/node_modules/@cap-js/db-service/lib/cqn2pql.js +1 -1
- package/node_modules/@cap-js/db-service/lib/cqn2sql.js +15 -11
- package/node_modules/@cap-js/db-service/lib/cqn4sql.js +119 -19
- package/node_modules/@cap-js/db-service/lib/infer/index.js +27 -16
- package/node_modules/@cap-js/db-service/lib/recursion-guard.js +40 -0
- package/node_modules/@cap-js/db-service/lib/search.js +1 -1
- package/node_modules/@cap-js/db-service/package.json +1 -1
- package/node_modules/@cap-js/sqlite/CHANGELOG.md +19 -0
- package/node_modules/@cap-js/sqlite/lib/SQLiteService.js +110 -14
- package/node_modules/@cap-js/sqlite/package.json +3 -3
- package/node_modules/@sap/cds-compiler/CHANGELOG.md +15 -0
- package/node_modules/@sap/cds-compiler/lib/compiler/resolve.js +7 -3
- package/node_modules/@sap/cds-compiler/lib/model/csnUtils.js +2 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/applyTransformations.js +12 -0
- package/node_modules/@sap/cds-compiler/lib/transform/db/associations.js +5 -3
- package/node_modules/@sap/cds-compiler/lib/transform/db/flattening.js +4 -3
- package/node_modules/@sap/cds-compiler/lib/transform/effective/flattening.js +17 -1
- package/node_modules/@sap/cds-compiler/lib/transform/effective/main.js +19 -0
- package/node_modules/@sap/cds-compiler/lib/transform/fioriTreeViews.js +12 -1
- package/node_modules/@sap/cds-compiler/lib/transform/forRelationalDB.js +1 -1
- package/node_modules/@sap/cds-compiler/lib/transform/odata/adaptAnnotationRefs.js +11 -5
- package/node_modules/@sap/cds-compiler/lib/transform/translateAssocsToJoins.js +29 -2
- package/node_modules/@sap/cds-compiler/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/CHANGELOG.md +34 -0
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx-migrate.js +26 -5
- package/node_modules/@sap/cds-mtxs/bin/cds-mtx.js +3 -0
- package/node_modules/@sap/cds-mtxs/lib/utils.js +5 -4
- package/node_modules/@sap/cds-mtxs/package.json +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/abstract-provisioning-service.js +62 -7
- package/node_modules/@sap/cds-mtxs/srv/cf/saas-provisioning-service.js +1 -1
- package/node_modules/@sap/cds-mtxs/srv/cf/sms-provisioning-service.js +23 -3
- package/node_modules/@sap/cds-mtxs/srv/extensibility/compat/update.js +15 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/annotations.js +2 -2
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/config.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/index.js +2 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility/linter/protected-namespace.js +45 -0
- package/node_modules/@sap/cds-mtxs/srv/extensibility-service.js +8 -3
- package/node_modules/@sap/cds-mtxs/srv/jobs-service.js +49 -10
- package/node_modules/@sap/cds-mtxs/srv/model-provider.js +11 -2
- package/node_modules/@sap/cds-mtxs/srv/plugins/common.js +19 -12
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/hdi-ctnr-mgr.js +6 -3
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana/serverless.js +271 -0
- package/node_modules/@sap/cds-mtxs/srv/plugins/hana.js +3 -2
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker-pool.js +212 -0
- package/node_modules/@sap/cds-mtxs/srv/worker/job-worker.js +61 -0
- package/package.json +2 -2
- /package/node_modules/@sap/cds-mtxs/srv/worker/{pool.js → model-worker-pool.js} +0 -0
- /package/node_modules/@sap/cds-mtxs/srv/worker/{load.js → model-worker.js} +0 -0
package/bin/import.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
const { FILE, DIR } = require('./completion')
|
|
1
2
|
module.exports = Object.assign(_import, {
|
|
2
3
|
_name: 'import',
|
|
3
4
|
staticCompletions: () => ({
|
|
4
5
|
'--from': ['asyncapi', 'edmx', 'openapi'],
|
|
5
6
|
'--as': ['cds', 'csn', 'json'],
|
|
6
|
-
'
|
|
7
|
+
'--out': DIR(),
|
|
8
|
+
'-o': DIR(),
|
|
9
|
+
'_': FILE('json,xml,edmx'),
|
|
7
10
|
}),
|
|
8
11
|
options: [ '--services', '--for', '--out', '--as', '--name', '--include-namespaces', '--dependencies', '--from', '--config', '--destination', '--group', '--into-namespace' ],
|
|
9
12
|
shortcuts: [ '-s', '-4', '-o', '-as', '-n', '-ns', '-d',undefined, '-c', undefined, undefined, '-2', '-', '-f', '-b'],
|
package/bin/init.js
CHANGED
|
@@ -10,6 +10,7 @@ module.exports = Object.assign(init, {
|
|
|
10
10
|
staticCompletions: () => ({
|
|
11
11
|
'--add': COMMA(require('../bin/completion/completionFs').readTemplates([])),
|
|
12
12
|
'--java:package': DIR(),
|
|
13
|
+
'--base': DIR(),
|
|
13
14
|
}),
|
|
14
15
|
options: ['--add', '--java:mvn', '--java:package', '--lint:custom', '--lint:custom:example', '--no-db', '--base'],
|
|
15
16
|
shortcuts: ['-a'],
|
package/bin/inspect.js
CHANGED
|
@@ -9,7 +9,9 @@ const { FILE } = require('./completion');
|
|
|
9
9
|
module.exports = Object.assign (inspect, {
|
|
10
10
|
staticCompletions: () => ({
|
|
11
11
|
'--kind': ['entity', 'aspect', 'type', 'service'],
|
|
12
|
-
'
|
|
12
|
+
'--list': ['names', 'elements', 'keys', 'associations', 'compositions', 'ids', 'sources', 'all'],
|
|
13
|
+
'-l': ['names', 'elements', 'keys', 'associations', 'compositions', 'ids', 'sources', 'all'],
|
|
14
|
+
'_': FILE('cds,json', { dirs: true }),
|
|
13
15
|
}),
|
|
14
16
|
options: [ '--stats', '--list', '--kind' ],
|
|
15
17
|
shortcuts: [ '-s', '-l', '-k' ],
|
package/bin/lint.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
const { FILE } = require('./completion')
|
|
1
2
|
const Linter = require('../lib/lint/Linter');
|
|
2
3
|
const linter = new Linter();
|
|
3
4
|
|
|
4
|
-
// cds cli exec expects an executable object with various additional properties.
|
|
5
|
-
// As most of the linting functionality is bound to a class instance, we need to wrap the instance
|
|
6
|
-
// to not lose the this-context and attach the additional properties to the function.
|
|
7
5
|
const lint = (...args) => linter.lint(...args)
|
|
8
6
|
const additionalProperties = Object.entries(linter.init())
|
|
9
7
|
.map(([key, value]) => [key, { value }])
|
|
10
8
|
|
|
11
9
|
Object.defineProperties(lint, Object.fromEntries(additionalProperties))
|
|
12
10
|
|
|
11
|
+
lint.staticCompletions = () => ({
|
|
12
|
+
'_': FILE('js,ts,cds', { dirs: true }),
|
|
13
|
+
})
|
|
14
|
+
|
|
13
15
|
module.exports = lint
|
package/bin/minify.js
CHANGED
|
@@ -119,4 +119,9 @@ class Dryifier extends Minifier {
|
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
-
|
|
122
|
+
const { FILE } = require('./completion')
|
|
123
|
+
module.exports = Object.assign (Minify._for_cds_dk(), { Minify,
|
|
124
|
+
staticCompletions: () => ({
|
|
125
|
+
'_': FILE('cds,json', { dirs: true }),
|
|
126
|
+
})
|
|
127
|
+
})
|
package/bin/repl.js
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
|
+
const { DIR } = require('./completion');
|
|
1
2
|
module.exports = Object.assign (repl, {
|
|
2
|
-
options: [ '--run', '--use'
|
|
3
|
-
|
|
3
|
+
options: [ '--run', '--use' ],
|
|
4
|
+
flags: [ '--ql' ],
|
|
5
|
+
shortcuts: ['-r', '-u'],
|
|
6
|
+
staticCompletions: () => ({
|
|
7
|
+
'_': DIR(),
|
|
8
|
+
'--run': DIR(),
|
|
9
|
+
'-r': DIR(),
|
|
10
|
+
'--use': ['xl', 'ql', 'entities', 'services'],
|
|
11
|
+
'-u': ['xl', 'ql', 'entities', 'services'],
|
|
12
|
+
}),
|
|
4
13
|
help: `
|
|
5
14
|
# SYNOPSIS
|
|
6
15
|
|
package/bin/run.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
|
+
const { DIR } = require('./completion');
|
|
1
2
|
const serve = require ('./serve')
|
|
2
3
|
module.exports = Object.assign (
|
|
3
|
-
cds_run, serve, {
|
|
4
|
+
cds_run, serve, {
|
|
5
|
+
staticCompletions: () => ({
|
|
6
|
+
'_': DIR(),
|
|
7
|
+
'--from': DIR(),
|
|
8
|
+
'-f': DIR(),
|
|
9
|
+
'--with': DIR(),
|
|
10
|
+
'-w': DIR(),
|
|
11
|
+
'--to': ['odata', 'fiori', 'rest'],
|
|
12
|
+
'-2': ['odata', 'fiori', 'rest'],
|
|
13
|
+
}),
|
|
14
|
+
help: `
|
|
4
15
|
# SYNOPSIS
|
|
5
16
|
|
|
6
17
|
*cds run* [<project>] [<options>]
|
package/bin/serve.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
// allows executing `cds serve` even when `cds` executable is removed from `@sap/cds`
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
const { DIR } = require('./completion');
|
|
3
|
+
const serve = require(_local('@sap/cds/bin/serve'))
|
|
4
|
+
module.exports = Object.assign(serve, {
|
|
5
|
+
staticCompletions: () => ({
|
|
6
|
+
'_': DIR(),
|
|
7
|
+
'--from': DIR(),
|
|
8
|
+
'-f': DIR(),
|
|
9
|
+
'--with': DIR(),
|
|
10
|
+
'-w': DIR(),
|
|
11
|
+
'--to': ['odata', 'fiori', 'rest'],
|
|
12
|
+
'-2': ['odata', 'fiori', 'rest'],
|
|
13
|
+
}),
|
|
14
|
+
})
|
|
4
15
|
|
|
5
16
|
function _local (id) {
|
|
6
17
|
return require.resolve (id, { paths: [ process.cwd(), __dirname ] })
|
package/bin/up.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
const { FILE } = require('./completion');
|
|
2
|
+
|
|
1
3
|
module.exports = Object.assign(up, {
|
|
2
4
|
staticCompletions: () => ({
|
|
3
|
-
'--to':
|
|
5
|
+
'--to': ['cf', 'k8s'],
|
|
6
|
+
'--overlay': FILE('mtaext')
|
|
4
7
|
}),
|
|
5
8
|
options: ['--to', '--namespace', '--overlay'],
|
|
6
9
|
shortcuts: ['-2', '-n'],
|
package/bin/util/containerize.js
CHANGED
|
@@ -59,6 +59,7 @@ function processCommands(bp) {
|
|
|
59
59
|
}
|
|
60
60
|
function processBuildpack(bp, name, tag, clearCache = false, image, publish = false) {
|
|
61
61
|
let { type, path, env, builder } = bp.buildpack
|
|
62
|
+
const dependencyMirrors = Object.keys(process.env).filter(key => key.startsWith('BP_DEPENDENCY_MIRROR_') && process.env[key])
|
|
62
63
|
type = type ? type.split(',').map(t => buildpacks.has(t.trim()) ? `paketo-buildpacks/${t.trim()}` : t.trim()) : []
|
|
63
64
|
if (builders.has(builder)) builder = `paketobuildpacks/${builder}`
|
|
64
65
|
const cacheArgs = clearCache ? ['--clear-cache'] : []
|
|
@@ -69,6 +70,7 @@ function processBuildpack(bp, name, tag, clearCache = false, image, publish = fa
|
|
|
69
70
|
...type.flatMap(t => appendSubcommand(t, '--buildpack')),
|
|
70
71
|
...appendSubcommand(builder, '--builder'),
|
|
71
72
|
...appendSubcommand(env, '--env'),
|
|
73
|
+
...dependencyMirrors.flatMap(key => ['--env', key]),
|
|
72
74
|
...(publish ? ['--publish'] : []),
|
|
73
75
|
...cacheArgs
|
|
74
76
|
]]
|
package/bin/watch/index.js
CHANGED
|
@@ -3,10 +3,17 @@ const { DIR } = require('../completion')
|
|
|
3
3
|
|
|
4
4
|
module.exports = Object.assign ( watch, {
|
|
5
5
|
staticCompletions: () => ({
|
|
6
|
+
'_': DIR(),
|
|
7
|
+
'--from': DIR(),
|
|
8
|
+
'-f': DIR(),
|
|
9
|
+
'--with': DIR(),
|
|
10
|
+
'-w': DIR(),
|
|
11
|
+
'--to': ['odata', 'fiori', 'rest'],
|
|
12
|
+
'-2': ['odata', 'fiori', 'rest'],
|
|
6
13
|
'--experimental-hot-reload': ['true', 'false'],
|
|
7
14
|
'--livereload': ['false'],
|
|
8
15
|
'--include': DIR(),
|
|
9
|
-
'--exclude': DIR()
|
|
16
|
+
'--exclude': DIR()
|
|
10
17
|
}),
|
|
11
18
|
flags: [ '--in-memory', '--in-memory?', '--with-mocks', '--with-mtx' ],
|
|
12
19
|
options: [ '--port', ...watchOnlyOptions ],
|
package/lib/build/util.js
CHANGED
|
@@ -8,7 +8,6 @@ const { exists } = cds.utils
|
|
|
8
8
|
const execAsync = require('node:util').promisify(cp.exec)
|
|
9
9
|
const { isRoot } = require('../util/fs')
|
|
10
10
|
|
|
11
|
-
const IS_WIN = require('node:os').platform() === 'win32'
|
|
12
11
|
const { SEVERITY_ERROR, FILE_EXT_CDS, NODEJS_MODEL_EXCLUDE_LIST } = require('./constants');
|
|
13
12
|
const DEBUG = cds.debug('cli|build')
|
|
14
13
|
|
|
@@ -288,7 +287,7 @@ const _cachedWsRoots = new Map()
|
|
|
288
287
|
* could contain globs. Glob resolution will be supported.
|
|
289
288
|
* The result is cached by workspace root!
|
|
290
289
|
* natively in the future (fs.glob is in experimental state as of Node23).
|
|
291
|
-
* @param {
|
|
290
|
+
* @param {string} wsRoot - the root directory of the workspace
|
|
292
291
|
* @returns {Promise<string[]>} list of package paths, or an empty array if either
|
|
293
292
|
* no workspaces are defined or have not been installed.
|
|
294
293
|
*/
|
|
@@ -302,8 +301,7 @@ async function _execNpmWs(wsRoot) {
|
|
|
302
301
|
try {
|
|
303
302
|
const cmdLine = 'npm ls --ws --json'
|
|
304
303
|
DEBUG?.(`execute ${cmdLine}`)
|
|
305
|
-
|
|
306
|
-
const { stdout } = await execAsync(cmdLine, { shell: IS_WIN, stdio: ['inherit', 'pipe', 'inherit'], cwd: wsRoot })
|
|
304
|
+
const { stdout } = await execAsync(cmdLine, { cwd: wsRoot })
|
|
307
305
|
const jsonResult = JSON.parse(stdout)
|
|
308
306
|
if (jsonResult.dependencies) {
|
|
309
307
|
result = Object.keys(jsonResult.dependencies)
|
package/lib/init/constants.js
CHANGED
|
@@ -247,6 +247,9 @@ function dataForField(element, artifact, csn, record, data, parent, depth, refer
|
|
|
247
247
|
const range = { from: 0, to: prec }
|
|
248
248
|
annotatedIntRange(element, range)
|
|
249
249
|
if (scale === 0) return randomIntInRange(range)
|
|
250
|
+
const factor = Math.pow(10, scale)
|
|
251
|
+
range.from = Math.ceil(range.from * factor) / factor
|
|
252
|
+
range.to = Math.floor(range.to * factor) / factor
|
|
250
253
|
// draw uniformly across the range and round to scale, so annotated bounds are never exceeded
|
|
251
254
|
const val = +randomDecimalInRange(range).toFixed(scale)
|
|
252
255
|
return +Math.min(Math.max(val, range.from), range.to).toFixed(scale)
|
|
@@ -4,6 +4,23 @@
|
|
|
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
|
+
## [3.1.0](https://github.com/cap-js/cds-dbs/compare/db-service-v3.0.1...db-service-v3.1.0) (2026-09-07)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
* rank $search results by relevance ([#1725](https://github.com/cap-js/cds-dbs/issues/1725)) ([bc93160](https://github.com/cap-js/cds-dbs/commit/bc931601a721f7c9e00b89e6844ba064352b541d))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
* **cqn4sql:** reject overly nested query expressions instead of crashing ([#1712](https://github.com/cap-js/cds-dbs/issues/1712)) ([29a45b7](https://github.com/cap-js/cds-dbs/commit/29a45b7daadbc77433b882148700d73c00efae62))
|
|
18
|
+
* don't throw when releasing/destroying a resource that's no longer in the pool ([#1696](https://github.com/cap-js/cds-dbs/issues/1696)) ([5d3b277](https://github.com/cap-js/cds-dbs/commit/5d3b277a1cf7cac02e41f2f24975a8e9473e5b5e))
|
|
19
|
+
* exclude cds.Vector from wildcard select expansion ([#1716](https://github.com/cap-js/cds-dbs/issues/1716)) ([398bacc](https://github.com/cap-js/cds-dbs/commit/398bacc2a74883dcd93acba288deae362570a4c1))
|
|
20
|
+
* INSERT.from(SELECT) used non-existent `value` ([#1565](https://github.com/cap-js/cds-dbs/issues/1565)) ([fd98844](https://github.com/cap-js/cds-dbs/commit/fd98844c83a78d6cf2b0b71344d3ee1d88528106))
|
|
21
|
+
* **pool:** recover lost connection after stale acquire timeout ([#1724](https://github.com/cap-js/cds-dbs/issues/1724)) ([cf6af99](https://github.com/cap-js/cds-dbs/commit/cf6af991d02be7cf5f861e2a424d4d733b8f9238))
|
|
22
|
+
* pql groupby path expression ([#1700](https://github.com/cap-js/cds-dbs/issues/1700)) ([82b5e03](https://github.com/cap-js/cds-dbs/commit/82b5e0385e680321f30c352f0f78ade6d25259a6))
|
|
23
|
+
|
|
7
24
|
## [3.0.1](https://github.com/cap-js/cds-dbs/compare/db-service-v3.0.0...db-service-v3.0.1) (2026-06-29)
|
|
8
25
|
|
|
9
26
|
|
|
@@ -150,7 +150,7 @@ constructor (factory, options = {}) {
|
|
|
150
150
|
return
|
|
151
151
|
}
|
|
152
152
|
const loan = this._loans.get(resource)
|
|
153
|
-
if (!loan)
|
|
153
|
+
if (!loan) return
|
|
154
154
|
this._loans.delete(resource)
|
|
155
155
|
const pooledResource = loan.pooledResource
|
|
156
156
|
pooledResource.idle()
|
|
@@ -164,7 +164,7 @@ constructor (factory, options = {}) {
|
|
|
164
164
|
return
|
|
165
165
|
}
|
|
166
166
|
const loan = this._loans.get(resource)
|
|
167
|
-
if (!loan)
|
|
167
|
+
if (!loan) return
|
|
168
168
|
this._loans.delete(resource)
|
|
169
169
|
const pooledResource = loan.pooledResource
|
|
170
170
|
await this.#destroy(pooledResource)
|
|
@@ -212,16 +212,13 @@ constructor (factory, options = {}) {
|
|
|
212
212
|
for (let i = 0; i < needed; i++) this.#createResource()
|
|
213
213
|
}
|
|
214
214
|
const dispense = async resource => {
|
|
215
|
-
|
|
215
|
+
let request = this._queue.shift()
|
|
216
|
+
while (request && request.state !== RequestState.PENDING) request = this._queue.shift()
|
|
216
217
|
if (!request) {
|
|
217
218
|
resource.idle()
|
|
218
219
|
this._available.add(resource)
|
|
219
220
|
return false
|
|
220
221
|
}
|
|
221
|
-
if (request.state !== RequestState.PENDING) {
|
|
222
|
-
this.#dispense()
|
|
223
|
-
return false
|
|
224
|
-
}
|
|
225
222
|
this._loans.set(resource.obj, { pooledResource: resource })
|
|
226
223
|
resource.update(ResourceState.ALLOCATED)
|
|
227
224
|
request.resolve(resource.obj)
|
|
@@ -18,7 +18,7 @@ class CQN2PQLRenderer extends CQN2SQL {
|
|
|
18
18
|
|
|
19
19
|
column_expr(x, q) {
|
|
20
20
|
// omit alias when target is a single source
|
|
21
|
-
if (q.SELECT.from
|
|
21
|
+
if (q.SELECT.from?.ref && x?.ref) x.ref = x.ref.slice(-1)
|
|
22
22
|
return super.column_expr(x, q)
|
|
23
23
|
}
|
|
24
24
|
|
|
@@ -1088,7 +1088,7 @@ class CQN2SQLRenderer {
|
|
|
1088
1088
|
|
|
1089
1089
|
const extractions = this._managed = this.managed(columns.map(c => ({ name: c, sql: `NEW.${this.quote(c)}` })), elements)
|
|
1090
1090
|
const sql = extractions.length > columns.length
|
|
1091
|
-
? `SELECT ${extractions.map(c => `${c.insert} AS ${this.quote(c.name)}`)} FROM (${this.SELECT(src)}) AS NEW`
|
|
1091
|
+
? `SELECT ${extractions.map((c, i) => `${i < columns.length ? c.insert : c.onInsert} AS ${this.quote(c.name)}`)} FROM (${this.SELECT(src)}) AS NEW`
|
|
1092
1092
|
: this.SELECT(src)
|
|
1093
1093
|
if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
|
|
1094
1094
|
this.sql = `INSERT INTO ${this.quote(entity)}${alias ? ' as ' + this.quote(alias) : ''} (${columns.map(c => this.quote(transitions.mapping.get(c)?.ref?.[0] || c))}) ${sql}`
|
|
@@ -1161,12 +1161,12 @@ class CQN2SQLRenderer {
|
|
|
1161
1161
|
const extractions = this._managed
|
|
1162
1162
|
if (this.values) this.values = [] // Clear previously computed values
|
|
1163
1163
|
const src = this.cqn4sql(UPSERT.from || UPSERT.as)
|
|
1164
|
-
const aliasedQuery =
|
|
1165
|
-
.columns(
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
sql = `SELECT ${extractions.map(c =>
|
|
1164
|
+
const aliasedQuery = `SELECT ${[
|
|
1165
|
+
...src.SELECT.columns.map((c, i) => this.column_expr({ ref: [this.column_name(c)], as: this.columns[i] })),
|
|
1166
|
+
...extractions.slice(src.SELECT.columns.length).map(c => `${elements[c.name].key ? c.onInsert : 'NULL'} AS ${this.quote(c.name)}`), // fill in missing default values
|
|
1167
|
+
]} FROM (${this.SELECT(src)})`
|
|
1168
|
+
|
|
1169
|
+
sql = `SELECT ${extractions.map(c => elements[c.name].key ? `NEW.${this.quote(c.name)}` : c.upsert)} FROM(${aliasedQuery}) AS NEW LEFT JOIN ${this.quote(entity)} AS OLD ON ${keyCompare} `
|
|
1170
1170
|
if (extractions.length > columns.length) columns = this.columns = extractions.map(c => c.name)
|
|
1171
1171
|
this.entries = [this.values]
|
|
1172
1172
|
}
|
|
@@ -1574,9 +1574,11 @@ class CQN2SQLRenderer {
|
|
|
1574
1574
|
|
|
1575
1575
|
managed_extract(name, element, converter) {
|
|
1576
1576
|
const { UPSERT, INSERT } = this.cqn
|
|
1577
|
-
const extract =
|
|
1578
|
-
? `value->>${this.string(
|
|
1579
|
-
:
|
|
1577
|
+
const extract = (INSERT?.entries || UPSERT?.entries)
|
|
1578
|
+
? `value->>${this.string(`$.${JSON.stringify(name)}`)}`
|
|
1579
|
+
: (INSERT?.rows || UPSERT?.rows)
|
|
1580
|
+
? `value->>${this.string(`$[${this.columns.indexOf(name)}]`)}`
|
|
1581
|
+
: `NEW.${this.quote(name)}` // in case of (INSERT?.from || UPSERT?.from)
|
|
1580
1582
|
const sql = converter?.(extract) || extract
|
|
1581
1583
|
return { extract, sql }
|
|
1582
1584
|
}
|
|
@@ -1587,7 +1589,9 @@ class CQN2SQLRenderer {
|
|
|
1587
1589
|
}
|
|
1588
1590
|
|
|
1589
1591
|
managed_default(name, managed, src) {
|
|
1590
|
-
|
|
1592
|
+
const { UPSERT, INSERT } = this.cqn
|
|
1593
|
+
const isJson = INSERT?.entries || UPSERT?.entries || INSERT?.rows || UPSERT?.rows
|
|
1594
|
+
return `(CASE WHEN ${isJson ? `json_type(value,${this.managed_extract(name).extract.slice(8)})` : `NEW.${this.quote(name)}`} IS NULL THEN ${managed} ELSE ${src} END)`
|
|
1591
1595
|
}
|
|
1592
1596
|
}
|
|
1593
1597
|
|
|
@@ -33,6 +33,9 @@ const notSupportedOps = [['>'], ['<'], ['>='], ['<='], ['*'], ['+'], ['-'], ['/'
|
|
|
33
33
|
|
|
34
34
|
const allOps = eqOps.concat(eqOps).concat(notEqOps).concat(notSupportedOps)
|
|
35
35
|
|
|
36
|
+
|
|
37
|
+
const { depthGuarded, guardEntry } = require('./recursion-guard')
|
|
38
|
+
|
|
36
39
|
const { pseudos } = require('./infer/pseudos')
|
|
37
40
|
/**
|
|
38
41
|
* Transforms a CDL style query into SQL-Like CQN:
|
|
@@ -55,7 +58,17 @@ const { pseudos } = require('./infer/pseudos')
|
|
|
55
58
|
* @returns {object} transformedQuery the transformed query
|
|
56
59
|
*/
|
|
57
60
|
function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
61
|
+
// guardEntry bounds subquery nesting and resets the depth budget per top-level call
|
|
62
|
+
return guardEntry(_cqn4sql)(originalQuery, model, useTechnicalAlias)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function _cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
58
66
|
const getImplicitAlias = str => _getImplicitAlias(str, useTechnicalAlias)
|
|
67
|
+
// guard xpr and inline/expand-on-struct nesting
|
|
68
|
+
// eslint-disable-next-line no-func-assign
|
|
69
|
+
getTransformedTokenStream = depthGuarded(getTransformedTokenStream)
|
|
70
|
+
// eslint-disable-next-line no-func-assign
|
|
71
|
+
nestedProjectionOnStructure = depthGuarded(nestedProjectionOnStructure)
|
|
59
72
|
let inferred = typeof originalQuery === 'string' ? cds.parse.cql(originalQuery) : cds.ql.clone(originalQuery)
|
|
60
73
|
const hasCustomJoins =
|
|
61
74
|
originalQuery.SELECT?.from.args && (!originalQuery.joinTree || originalQuery.joinTree.isInitial)
|
|
@@ -69,6 +82,9 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
69
82
|
const { where, having } = transformSearch(searchTerm)
|
|
70
83
|
if (where) inferred.SELECT.where = where
|
|
71
84
|
else if (having) inferred.SELECT.having = having
|
|
85
|
+
// Defer the ranking ORDER BY to after infer(), where the outer table alias is known and the
|
|
86
|
+
// deep-search sub-select can be correlated to the outer row (see buildSearchRankOrderBy).
|
|
87
|
+
defineProperty(inferred, '$searchRank', searchTerm)
|
|
72
88
|
}
|
|
73
89
|
}
|
|
74
90
|
// query modifiers can also be defined in from ref leaf infix filter
|
|
@@ -182,7 +198,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
182
198
|
/**
|
|
183
199
|
* If the target entity is annotated with persistence skip and has an underlying db entity,
|
|
184
200
|
* we treat it as a runtime view and transform it into a CTE.
|
|
185
|
-
*
|
|
201
|
+
*
|
|
186
202
|
* @param {object} transformedQuery - The query object to be transformed.
|
|
187
203
|
* @param {string} model - The data model used for inference and transformation.
|
|
188
204
|
*/
|
|
@@ -198,10 +214,10 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
198
214
|
}
|
|
199
215
|
|
|
200
216
|
/**
|
|
201
|
-
* Recursively call cqn4sql for all nested runtime views to calculate cte and
|
|
217
|
+
* Recursively call cqn4sql for all nested runtime views to calculate cte and
|
|
202
218
|
* add it as a with clause to the transformed query.
|
|
203
219
|
* Alias the runtime view with a unique alias and update all references to the runtime view to point to the alias.
|
|
204
|
-
*
|
|
220
|
+
*
|
|
205
221
|
* @param {object} rootDefinition - The root definition of the query. This is used to recursively process nested runtime views.
|
|
206
222
|
* @param {object} transformedQuery - The query object to be transformed.
|
|
207
223
|
* @param {string} model - The data model used for infer and cqn4sql.
|
|
@@ -317,9 +333,29 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
317
333
|
|
|
318
334
|
// Since all the expressions in the SELECT part of the query have been computed,
|
|
319
335
|
// one can reference aliases of the queries columns in the orderBy clause.
|
|
320
|
-
|
|
321
|
-
|
|
336
|
+
let effectiveOrderBy = orderBy
|
|
337
|
+
// Rank by $search relevance — only when the score exists (HANA fuzzy, not opted out); else
|
|
338
|
+
// it would sort by a constant boolean.
|
|
339
|
+
const ranksSearch =
|
|
340
|
+
cds.db?.kind === 'hana' && cds.env.hana?.fuzzy !== false && cds.env.hana?.fuzzy?.ranked_search !== false
|
|
341
|
+
// count queries do not need ranked search
|
|
342
|
+
const isCountQuery = columns?.length === 1 && columns[0].func === 'count'
|
|
343
|
+
const searchRank = ranksSearch && !isCountQuery && inferred.$searchRank && buildSearchRankOrderBy(inferred.$searchRank)
|
|
344
|
+
if (searchRank) {
|
|
345
|
+
// precedence: user ordering, then rank, then the runtime's implicit key ordering
|
|
346
|
+
const implicitAt = (orderBy || []).findIndex(o => o.implicit)
|
|
347
|
+
const at = implicitAt === -1 ? (orderBy?.length ?? 0) : implicitAt
|
|
348
|
+
effectiveOrderBy = [...(orderBy || [])]
|
|
349
|
+
effectiveOrderBy.splice(at, 0, searchRank)
|
|
350
|
+
}
|
|
351
|
+
if (effectiveOrderBy) {
|
|
352
|
+
const transformedOrderBy = getTransformedOrderByGroupBy(effectiveOrderBy, true)
|
|
322
353
|
if (transformedOrderBy.length) {
|
|
354
|
+
// the rank is the only order-by entry that is a correlated sub-select
|
|
355
|
+
if (searchRank?.$searchRank) {
|
|
356
|
+
const rank = transformedOrderBy.find(o => o.SELECT)
|
|
357
|
+
if (rank) correlateSearchRank(rank, transformedFrom.as)
|
|
358
|
+
}
|
|
323
359
|
transformedQuery.SELECT.orderBy = transformedOrderBy
|
|
324
360
|
}
|
|
325
361
|
}
|
|
@@ -383,6 +419,8 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
383
419
|
*/
|
|
384
420
|
function translateAssocsToJoins() {
|
|
385
421
|
let from
|
|
422
|
+
// guard association-path length
|
|
423
|
+
const joinForBranch = depthGuarded(_joinForBranch)
|
|
386
424
|
/**
|
|
387
425
|
* remember already seen aliases, do not create a join for them again
|
|
388
426
|
*/
|
|
@@ -406,7 +444,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
406
444
|
})
|
|
407
445
|
return from.args.length > 1 ? from : from.args[0]
|
|
408
446
|
|
|
409
|
-
function
|
|
447
|
+
function _joinForBranch(lhs, node) {
|
|
410
448
|
const nextAssoc = inferred.joinTree.findNextAssoc(node)
|
|
411
449
|
if (!nextAssoc || alreadySeen.has(nextAssoc.$refLink.alias)) return lhs.args.length > 1 ? lhs : lhs.args[0]
|
|
412
450
|
|
|
@@ -643,7 +681,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
643
681
|
if (col.cast) ret.cast = resolveEnumCastType(col.cast)
|
|
644
682
|
return ret
|
|
645
683
|
}
|
|
646
|
-
return
|
|
684
|
+
return { ...col }
|
|
647
685
|
}
|
|
648
686
|
|
|
649
687
|
function handleEmptyColumns(columns) {
|
|
@@ -995,11 +1033,11 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
995
1033
|
const keyName = k.as || k.ref.join('_')
|
|
996
1034
|
const fkName = `${elemName}_${keyName}` // e.g., 'head_id'
|
|
997
1035
|
const fkFullName = `${columnAlias}_${fkName}` // e.g., 'department_head_id'
|
|
998
|
-
|
|
1036
|
+
|
|
999
1037
|
// Check if this FK is excluded
|
|
1000
1038
|
if (exclude.some(e => (e.ref?.at(-1) || e.as || e) === fkName)) continue
|
|
1001
1039
|
if (exclude.some(e => (e.ref?.at(-1) || e.as || e) === fkFullName)) continue
|
|
1002
|
-
|
|
1040
|
+
|
|
1003
1041
|
const flatColumn = {
|
|
1004
1042
|
ref: [joinAlias, fkName],
|
|
1005
1043
|
as: fkFullName,
|
|
@@ -1017,7 +1055,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1017
1055
|
calcElement.as = fullName
|
|
1018
1056
|
}
|
|
1019
1057
|
res.push(calcElement)
|
|
1020
|
-
}
|
|
1058
|
+
}
|
|
1021
1059
|
else {
|
|
1022
1060
|
// Scalar element
|
|
1023
1061
|
const flatColumn = {
|
|
@@ -1322,7 +1360,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1322
1360
|
else if (col.xpr) transformedColumn = { xpr: getTransformedTokenStream(col.xpr) }
|
|
1323
1361
|
else if (col.func) transformedColumn = { args: getTransformedFunctionArgs(col.args), func: col.func }
|
|
1324
1362
|
// val
|
|
1325
|
-
else transformedColumn =
|
|
1363
|
+
else transformedColumn = { ...col }
|
|
1326
1364
|
if (col.sort) transformedColumn.sort = col.sort
|
|
1327
1365
|
if (col.nulls) transformedColumn.nulls = col.nulls
|
|
1328
1366
|
res.push(transformedColumn)
|
|
@@ -1397,8 +1435,13 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1397
1435
|
if (!exclude.includes(k)) {
|
|
1398
1436
|
const { index, tableAlias } = inferred.$combinedElements[k][0]
|
|
1399
1437
|
const element = tableAlias.elements[k]
|
|
1400
|
-
// ignore FK for odata csn (but not for subquery sources where FK is not a separate element) / ignore blobs from wildcard expansion
|
|
1401
|
-
if (
|
|
1438
|
+
// ignore FK for odata csn (but not for subquery sources where FK is not a separate element) / ignore blobs and vectors from wildcard expansion
|
|
1439
|
+
if (
|
|
1440
|
+
(!tableAlias.SELECT && isManagedAssocInFlatMode(element)) ||
|
|
1441
|
+
element.type === 'cds.LargeBinary' ||
|
|
1442
|
+
element.type === 'cds.Vector'
|
|
1443
|
+
)
|
|
1444
|
+
continue
|
|
1402
1445
|
// for wildcard on subquery in from, just reference the elements
|
|
1403
1446
|
if (tableAlias.SELECT && !element.elements && !element.target) {
|
|
1404
1447
|
wildcardColumns.push(index ? { ref: [index, k] } : { ref: [k] })
|
|
@@ -1461,7 +1504,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1461
1504
|
* - `tableAlias` — table alias prepended to the column ref.
|
|
1462
1505
|
* @param {string[]} [csnPath=[]] - Accumulated CSN element path (used for `_csnPath` metadata on leaf columns).
|
|
1463
1506
|
* @param {{ exclude?: Array, replace?: Array }} [excludeAndReplace] - Columns to exclude or replace during wildcard expansion.
|
|
1464
|
-
* @param {boolean} [isWildcard=false] - Whether this expansion originates from a wildcard; filters out LargeBinary.
|
|
1507
|
+
* @param {boolean} [isWildcard=false] - Whether this expansion originates from a wildcard; filters out LargeBinary and Vector.
|
|
1465
1508
|
* @returns {object[]} Flat column(s) for the given element.
|
|
1466
1509
|
*/
|
|
1467
1510
|
function getFlatColumnsFor(column, names, csnPath = [], excludeAndReplace, isWildcard = false) {
|
|
@@ -1474,7 +1517,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1474
1517
|
const { $refLinks, flatName, isJoinRelevant } = column
|
|
1475
1518
|
let firstNonJoinRelevantAssoc, stepAfterAssoc
|
|
1476
1519
|
let element = $refLinks ? $refLinks[$refLinks.length - 1].definition : column
|
|
1477
|
-
if (isWildcard && element.type === 'cds.LargeBinary') return []
|
|
1520
|
+
if (isWildcard && (element.type === 'cds.LargeBinary' || element.type === 'cds.Vector')) return []
|
|
1478
1521
|
if (element.on && !element.keys) return [] // unmanaged doesn't make it into columns
|
|
1479
1522
|
if (element.virtual === true) return []
|
|
1480
1523
|
|
|
@@ -1812,8 +1855,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1812
1855
|
// reject virtual elements in expressions as they will lead to a sql error down the line
|
|
1813
1856
|
if (lhsDef?.virtual) throw new Error(`Virtual elements are not allowed in expressions`)
|
|
1814
1857
|
|
|
1815
|
-
let result = is_regexp(token?.val) ? token :
|
|
1816
|
-
// REVISIT: required because we copy the token above and lose the not enumerable "param: false"
|
|
1858
|
+
let result = typeof token !== 'object' || is_regexp(token?.val) ? token : { ...token }
|
|
1817
1859
|
if (typeof token === 'object' && 'val' in token && 'param' in token) Object.defineProperty(result, 'param', { value: token.param })
|
|
1818
1860
|
if (token.ref) {
|
|
1819
1861
|
const { definition } = token.$refLinks.at(-1)
|
|
@@ -1830,7 +1872,6 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
1830
1872
|
const stepToFind = token.ref[1]?.id || token.ref[1]
|
|
1831
1873
|
const outerAlias = outerQuery.$combinedElements?.[stepToFind]?.[0].index
|
|
1832
1874
|
if (outerAlias) {
|
|
1833
|
-
let result = copy(token)
|
|
1834
1875
|
result.ref = [outerAlias, token.flatName]
|
|
1835
1876
|
transformedTokenStream.push(result)
|
|
1836
1877
|
continue
|
|
@@ -2016,7 +2057,7 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
2016
2057
|
*/
|
|
2017
2058
|
function getTransformedFrom(from, existingWhere = []) {
|
|
2018
2059
|
const transformedWhere = []
|
|
2019
|
-
let transformedFrom =
|
|
2060
|
+
let transformedFrom = { ...from }
|
|
2020
2061
|
if (from.$refLinks) defineProperty(transformedFrom, '$refLinks', [...from.$refLinks])
|
|
2021
2062
|
if (from.args) {
|
|
2022
2063
|
transformedFrom.args = []
|
|
@@ -2579,6 +2620,65 @@ function cqn4sql(originalQuery, model, useTechnicalAlias = true) {
|
|
|
2579
2620
|
return { xpr: [matchColumns.length === 1 ? matchColumns[0] : { list: matchColumns }, 'in', subquery] }
|
|
2580
2621
|
}
|
|
2581
2622
|
|
|
2623
|
+
/**
|
|
2624
|
+
* Builds the ORDER BY entry ranking rows by $search relevance, sorted desc.
|
|
2625
|
+
*
|
|
2626
|
+
* Flat search: the score is on the outer row.
|
|
2627
|
+
* Deep search: the score lives in a semi-join, so emit a correlated scalar sub-select
|
|
2628
|
+
* (SELECT search(…, true) FROM <same source> WHERE innerKey = <outerAlias>.key) DESC.
|
|
2629
|
+
* Key comparisons are seeded unqualified (infer() binds them to the sub-select's own source);
|
|
2630
|
+
* correlateSearchRank redirects the rhs to the outer alias afterwards.
|
|
2631
|
+
*
|
|
2632
|
+
* @param {object} searchTerm the search term as returned by getSearch (func or xpr shape)
|
|
2633
|
+
* @returns {object|null} an orderBy entry, or null if there is nothing to rank by
|
|
2634
|
+
*/
|
|
2635
|
+
function buildSearchRankOrderBy(searchTerm) {
|
|
2636
|
+
if (searchTerm.func) return { func: searchTerm.func, args: [...searchTerm.args, { val: true }], sort: 'desc' }
|
|
2637
|
+
if (!searchTerm.xpr) return null
|
|
2638
|
+
|
|
2639
|
+
const searchSelect = searchTerm.xpr[2]
|
|
2640
|
+
const searchFunc = searchSelect.SELECT.where[0]
|
|
2641
|
+
const innerKeys = searchSelect.SELECT.columns // unqualified pk refs, e.g. [{ ref: ['ID'] }]
|
|
2642
|
+
|
|
2643
|
+
const where = []
|
|
2644
|
+
for (let i = 0; i < innerKeys.length; i++) {
|
|
2645
|
+
if (i) where.push('and')
|
|
2646
|
+
// seeded unqualified on both sides; correlateSearchRank redirects the rhs to the outer row
|
|
2647
|
+
where.push({ ref: [...innerKeys[i].ref] }, '=', { ref: [...innerKeys[i].ref] })
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2650
|
+
const entry = {
|
|
2651
|
+
__proto__: SELECT.from(searchSelect.SELECT.from)
|
|
2652
|
+
// the correlated outer row fans out to many child rows -> MAX makes it a single score
|
|
2653
|
+
.columns({ func: 'max', args: [{ func: searchFunc.func, args: [...searchFunc.args, { val: true }] }] })
|
|
2654
|
+
.where(where),
|
|
2655
|
+
sort: 'desc',
|
|
2656
|
+
}
|
|
2657
|
+
defineProperty(entry, '$searchRank', true)
|
|
2658
|
+
return entry
|
|
2659
|
+
}
|
|
2660
|
+
|
|
2661
|
+
/**
|
|
2662
|
+
* Correlates the (transformed) deep-search ranking sub-select to the outer row.
|
|
2663
|
+
*
|
|
2664
|
+
* buildSearchRankOrderBy seeds its WHERE as `ref = ref` comparisons on the sub-select's own
|
|
2665
|
+
* alias; this rewrites each rhs to `<outerAlias>.<key>`. Driven off the `=` operator (not a
|
|
2666
|
+
* fixed stride) so it holds for any key count.
|
|
2667
|
+
*
|
|
2668
|
+
* @param {object} entry the transformed orderBy entry produced from a `$searchRank` sub-select
|
|
2669
|
+
* @param {string} outerAlias the final table alias of the outer query source
|
|
2670
|
+
*/
|
|
2671
|
+
function correlateSearchRank(entry, outerAlias) {
|
|
2672
|
+
const where = entry.SELECT.where
|
|
2673
|
+
for (let i = 1; i < where.length; i++) {
|
|
2674
|
+
// seeded comparisons are exactly `<ref> = <ref>`; rewrite the rhs ref to the outer row
|
|
2675
|
+
if (where[i] === '=' && where[i - 1]?.ref && where[i + 1]?.ref) {
|
|
2676
|
+
const rhs = where[i + 1]
|
|
2677
|
+
rhs.ref = [outerAlias, ...rhs.ref.slice(1)]
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
|
|
2582
2682
|
/**
|
|
2583
2683
|
* Calculates the name of the source which can be used to address the given node.
|
|
2584
2684
|
*
|