@sap/cds
Version:
SAP Cloud Application Programming Model - CDS for Node.js
133 lines (115 loc) • 4.4 kB
JavaScript
const { LinkedDefinitions, struct, type } = require ('./classes')
const _is_cached = Symbol('is cached')
const _derived = Symbol('derived')
class entity extends struct {
is (kind) { return kind === 'entity' || kind === 'view' && !!this.query || super.is(kind) }
/**
* Experimental API to write:
* ```js
* SELECT.from(Foo.as('bar')) // ... as alternative to:
* SELECT.from(Foo).alias('bar')
* ```
*/
as (alias) { return { ntt:this, ref:[this.name], as:alias } }
get keys() {
return this._elements ('key')
}
get associations() {
return this._elements ('isAssociation')
}
get compositions() {
return this._elements ('isComposition')
}
_elements (kind, _default = null) {
const derived = this.own(_derived, ()=>({}))
if (kind in derived) return derived[kind]
const {elements} = this, dict = new LinkedDefinitions; let any = _default
for (let e in elements) if (elements[e][kind]) (any = dict)[e] = elements[e]
return derived[kind] = any
}
toJSON() {
return this.own('query') ? {...this} : super.toJSON(this)
}
toString() {
return this.name.replace(/\./g,'_')
}
}
class Association extends type {
is(kind) { return kind === 'Association' || super.is(kind) }
get is2many() { return !this.is2one }
get is2one() {
let c = this.cardinality
return !c || c.max === 1 || (!c.max && !c.targetMax) || c.targetMax === 1
}
set foreignKeys(k) { this.set('foreignKeys', k) }
get foreignKeys() {
const keys = this.keys; if (!keys) return this.foreignKeys = undefined
if (!Object.keys(keys).length) return this.foreignKeys = null
const foreignKeys = new LinkedDefinitions
for (const k of keys) {
const el = k.ref.reduce((target,n)=> target.elements[n], this._target)
const as = k.as || k.ref.at(-1)
foreignKeys[as] = Object.create (el, { name:{value:as}, notNull: {value:true} }) //, parent:{value:this} })
// For structures, foreign keys must be provided, (autogenerated) UUIDs are skipped for validation
// REVISIT: We should change the line above to set parent correctly, as in the commented tail part.
// But that breaks code in _runtime which expects it to be the wrong parent.
}
return this.foreignKeys = foreignKeys
}
set keys(k) { super.keys = k }
get keys() {
if (this.on || this.is2many || !this._target) return this.set('_keys', undefined)
const keys=[], tks = this._target.keys
for (let k in tks) keys.push({ ref: [tks[k].name] })
return this.keys = keys
}
/**
* Gets the foreign key data for a given managed association from inbound data
* converted to an object that can be used as a where clause filter to fetch
* from the assoc's target entity. The function correctly handles renamed
* foreign keys.
*
* @example
* entity Books { //...
* author : Association to Authors { ID:id } // renamed foreign key
* }
*
* @example
* let { Books, Authors } = srv.entities
* let { author } = Books.elements
* let data = { // inbound data, e.g. from req.data
* title: 'Foo',
* author_id: 111
* }
* let entry = author.refIn(data)
* //> { ref: [ {id:'Authors', where:[ {ref:['ID']}, '=', {val:111} ]} ]}
* await SELECT.one(entry)
* await db.exists(entry)
*/
refIn (d) {
if (!this.keys) throw new Error (`Can't collect filter data for unmanaged associations`)
const value = this.dataIn(d); if (!value) return
const where={}; for (let {ref:[r],as} of this.keys) where[r] = value[as||r]
const q = SELECT.from (this._target, where)
return q.SELECT.from
}
/**
* Overriding implementation in base class to handle data for to-many Associations.
*/
dataIn (d, prefix='') {
if (this.is2many) {
const key = this.name, _key = '_'+key; if (_key in d) return d[_key]
const input = d[key]; if (input[_is_cached]) return input
if (Array.isArray(input)) {
const value = d[key].map (each => this._target.dataIn (each,'',true))
if (!prefix && d._hull) d._hull[key] = Object.defineProperty(value, _is_cached, {value:true})
return value
}
}
else return struct.prototype.dataIn.call (this, d, prefix)
}
}
class Composition extends Association {
is(kind) { return kind === 'Composition' || super.is(kind) }
}
module.exports = { entity, Association, Composition }