UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

149 lines (127 loc) 5.24 kB
/* eslint-disable no-useless-assignment */ const cds = require('../index') const infer = exports = module.exports = (..._) => infer.target(..._) /** * Infers the target entity of a query * @param {import('./cds.ql-Query')} q */ infer.target = function (q, ctx) { if (q._target instanceof cds.entity) return q._target //> already inferred const subject = _subject4(q) const target = ( subject?.SELECT ? infer.target (subject, ctx ??= _context4(q)) : subject?.ref ? infer.ref (subject.ref, ctx ??= _context4(q)) : undefined ) Object.defineProperty (q, '_target', { value: target, configurable:true, writable:true }) return target } infer.ref = function (ref, ctx) { const defs = (ctx.model||ctx).definitions let name = _name4(ref[0]), d = defs?.[name] || ctx.entities?.[name] if (!d) return { name, _unresolved:true, __proto__: cds.entity.prototype } else for (let i=1; i < ref.length; ++i) { d = d.elements?.[name = _name4(ref[i])] || cds.error `Element ${name} not found in ${d}` if (d.target) d = defs[d.target] || cds.error `Target ${d.target} not found in ${defs}` } return d } /** @param {import('./SELECT').class} q */ infer.elements = function (q) { const subject = _subject4(q); if (!subject) return const source = subject.SELECT ? cds.ql(subject) : infer.target(q) return _infer_elements4 (source, q.SELECT.columns, q.SELECT.excluding, q.SELECT.from.as) function _infer_elements4 (source, columns, excluding, alias) { // SELECT from Books; SELECT * from Books if (!columns || columns.length === 1 && columns[0] === '*') if (!excluding) return source?.elements const excludes = excluding?.reduce((p,n)=>(p[n]=1,p),{}) || {} const elements = {} columns.forEach (c => { // 1) SELECT *, ... from Books if (c === '*') { for (let e in source.elements) e in elements || e in excludes || (elements[e] = source.elements[e]) return } const ref = c.ref?.map(_name4); if (ref?.length > 1 && alias === ref[0]) ref.shift() const as = c.as || ref?.join('_') || c.func || c.val || cds.error `Alias required for column expressions ${c}` let d = source, is2many // 2.1) SELECT ... : String from Books if (c.cast) { return elements[as] = builtin [c.cast.type] } // 2.2) SELECT cast (... as String) from Books if (c.xpr?.[0]?.cast) { return elements[as] = builtin [c.xpr[0].cast.type] } // 3) SELECT title, author.name, $user.id from Books if (c.ref && d?.elements) { if (ref[0] in $pseudos) { d = $pseudos[ref[0]]; ref.shift() } for (let r of ref) d = (d.SELECT ? d : d._target||d).elements?.[r] || cds.error `Couldn't resolve element "${ref.join('/')}" in ${source.kind} ${source.name||''} ${Object.keys(source.elements)}` if (d._target) { is2many = d.is2many; c.expand || c.inline ? d = d._target : d } // ... d is further processed in steps 5,6,7 below } // 4) SELECT 1, 2+3, count(*) from Books; SELECT type, name from sqlite.schema else if (!c.expand) { return elements[as] = _typeof(c) } // 5) SELECT author.books { title } from Books if (c.expand) { if (d.items) { d = d.items; is2many = true } const nested = _infer_elements4 (d, c.expand, c.excluding) const struct = new cds.struct ({ elements: nested }) return elements[as] = is2many ? new cds.array ({ items:struct }) : struct } // 6) SELECT author.books.{ title } from Books if (c.inline) { const nested = _infer_elements4 (d, c.inline, c.excluding) for (let n in nested) elements[as+'_'+n] = nested[n] } // 7) SELECT title, author.name from Books else return elements[as] = d // NOTE: the else is neccessary after step 5 above // else return elements[as] = as === d.name ? d : { __proto__:d, name:as } }) return elements } } // REVISIT: should this prefer cds.context.model over q._srv? const _context4 = q => q._srv ?? cds.context?.model ?? cds.model ?? {} const _subject4 = q => q._subject || q.SELECT?.from || q.INSERT?.into || q.UPSERT?.into || q.UPDATE?.entity || q.DELETE?.from const _name4 = r => r.id || r const _typeof = c => { if (c.val !== undefined) return builtin [typeof c.val] || builtin [ Number.isInteger(c.val) ? 'Integer' : 'Decimal' ] if (c.func === 'count') return builtin.Integer if (c.xpr?.length === 1) return _typeof(c.xpr[0]) if (c.xpr) return { type: null } return unknown } const unknown = exports.unknown = Object.freeze (new cds.type ({ _unresolved:true })) const builtin = function() { const bi={}, bt = cds.builtin.types for (let t of Object.keys(bt)) bi[t] = bi[t.slice(4)] = { type:t, __proto__: bt[t] } bi.boolean = bi['cds.Boolean'] bi.string = bi['cds.String'] return bi }() const $pseudos = { $user: { __proto__: builtin.String, elements: { id: builtin.String, locale: builtin.String, tenant: builtin.String } }, $now: builtin.Timestamp, $at: builtin.Timestamp, $from: builtin.Timestamp, $to: builtin.Timestamp, $locale: builtin.String, $tenant: builtin.String, }