UNPKG

@sap/cds

Version:

SAP Cloud Application Programming Model - CDS for Node.js

229 lines (203 loc) 7.65 kB
const cds = require ('../index') /** * This is the default export, accessible to users through cds.ql. * It's a function turning given input into an instance of cds.ql.Query. * * - If input is already a Query, it is returned as is. * - If input is a string, it is parsed into a CQN object. * - If input is a CQN object, an instance of the corresponding * Query subclass is contructed and returned. * * Use it in a cast-like way like this: * * @example * let q = cds.ql ({ SELECT: { from: {ref:[ Books.name ]} }}) * let q = cds.ql (CQL`SELECT from ${Books}`) * let q = cds.ql `SELECT from ${Books}` * * @returns { import('./SELECT').class }, or any other subclass of Query; but IntelliSense doen't allow to specify this */ const ql = module.exports = exports = (q,...etc) => { if (q instanceof Query) return q if (q.raw || typeof q === 'string') return ql (cds.parse.cql(q,...etc)) for (let k in q) if (k in ql) return new ql[k](q[k]) return q //> no-op } // Base class and subclasses for all kinds of queries... const Query = exports.Query = require('./cds.ql-Query'); exports.SELECT = require('./SELECT') exports.INSERT = require('./INSERT') exports.UPSERT = require('./UPSERT') exports.UPDATE = require('./UPDATE') exports.DELETE = require('./DELETE') exports.CREATE = require('./CREATE') exports.DROP = require('./DROP') exports.predicate = require('./cds.ql-predicates') exports.columns = require('./cds.ql-projections') /** @import cqn from './cqn' */ /** * Constructs a CXN `{ref}` object from given input, which can be one of: * * - several path segment strings * - a single array of the same * - a tagged template literal in CXL path syntax * * @returns {cqn.ref} */ exports.ref = function (...ref) { if (ref[0].raw) ref = String.raw(...ref).split('.') return {ref} } /** * Constructs CXN `{val}` object from given input, which can be one of: * - a single `string`, `number`, `boolean`, or `null` * - a tagged template literal in CXL literal syntax * * @example * val(`foo`) //> {val:'foo'}` * val`foo` //> {val:'foo'} * val`11` //> {val:11} * val(11) //> {val:11} * * @returns {cqn.val} */ exports.val = (...val) => { if (val?.[0]?.raw) val = String.raw(...val) else [val] = val return {val} } /** * Constructs a CXN `xpr` object from given input, which can be one of: * * - multiple CXN `expr` objects, or strings representing keywords or operators * - a single array of the same * - a tagged template literal in CXL syntax * * @example * xpr([ref`foo`,'=',val(11)]) //> {xpr:[{ref:['foo']},'=',{val:11}]} * xpr(ref`foo`,'=',val(11)) //> {xpr:[{ref:['foo']},'=',{val:11}]} * xpr`foo = 11` //> {xpr:[{ref:['foo']},'=',{val:11}]} * xpr`foo` //> {xpr:[{ref:['foo']}]} * xpr`'foo'` //> {xpr:[{val:'foo'}]} * xpr`11` //> {xpr:[{val:11}]} * xpr('=') //> {xpr:['=']} * xpr('like') //> {xpr:['like']} * * @see {@link ql.expr `expr`} * @returns {cqn.xpr} */ exports.xpr = (...xpr) => { const x = ql.expr(...xpr) return x.xpr ? x : {xpr:[x]} // always returns an `{xpr}` object } /** * Same as {@link ql.xpr `xpr`}, but if the result contains only single * entries these are returned as is. * * @example * expr([ref`foo`,'=',val(11)]) //> {xpr:[{ref:['foo']},'=',{val:11}]} * expr(ref`foo`,'=',val(11)) //> {xpr:[{ref:['foo']},'=',{val:11}]} * expr`foo = 11` //> {xpr:[{ref:['foo']},'=',{val:11}]} * expr`foo` //> {ref:['foo']} * expr`11` //> {val:11} * * @returns { cqn.ref & cqn.val & cqn.xpr & cqn.list & cqn.func } the constructed CXN `expr` object. */ exports.expr = (...xpr) => { const [x] = xpr; if (x?.raw) return cds.parse.expr(...xpr) //> tagged template literal else if (is_array(x)) xpr = x //> entries are supposed to be CXN objects return xpr.length === 1 ? xpr[0] : {xpr} //> single entries are returned as is } /** * Constructs a CXN `list` object from given input, with can be one of: * * - multiple CXN `expr` objects, or values turned into `{val}`s, including strings * - a single array of the same * * @example * list([`foo`,11]) //> {list:[{val:'foo'},{val:11}]} * list(`foo`,11) //> {list:[{val:'foo'},{val:11}]} * expr`'foo',11` //> {list:[{val:'foo'},{val:11}]} * expr`foo,11` //> {list:[{ref:['foo']},{val:11}]} * * @see Use {@link ql.expr `expr()`} to get the same via a tagged template literal. * @returns {cqn.list} */ exports.list = (...args) => { const [x] = args; if (is_array(x)) args = x return { list: args.map (_cqn_or_val) } } /** * Constructs a CXN `func` object from given input. The first argument is the * function name, the remaining `args` can the same as in {@link ql.list `list()`}, * and are handled the same way. * * @example * func('substring',[`foo`,1]) //> {func:'substring',args:[{val:'foo'},{val:1}]} * func('substring',`foo`,1) //> {func:'substring',args:[{val:'foo'},{val:1}]} * expr`substring('foo',1)` //> {func:'substring',args:[{val:'foo'},{val:1}]} * expr`substring(foo,1)` //> {func:'substring',args:[{ref:['foo']},{val:1}]} * expr`substring(foo,1)` //> {func:'substring',args:[{ref:['foo']},{val:1}]} * * @see Use {@link ql.expr `expr()`} to get the same via a tagged template literal. * @returns {cqn.func} */ exports.func = (func,...args) => { const [x] = args; if (is_array(x)) args = x return { func, args: args.map (_cqn_or_val) } } /** @returns { cqn.ref & cqn.as & cqn.infix &{ columns: cqn.column[] }} */ exports.nested = (ref, ...args) => { if (ref.raw) return ql.nested (ql.ref(ref,...args)) else if (!ref.ref) ref = ql.ref(ref) for (let each of args) { if (each.as || each.where || each.orderBy || each.limit) ref = {...ref, ...each} else ref.columns = ql.columns(each) } ref.columns ??= ['*'] return ref } exports.expand = (...args) => { let { columns, ...rest } = ql.nested (...args) return { ...rest, expand: columns } } exports.inline = (...args) => { let { columns, ...rest } = ql.nested (...args) return { ...rest, inline: columns } } /** @returns {{ where: cqn.xo[] }} */ exports.where = (...args) => ({where: ql.predicate(...args)}) /** @returns {{ orderBy: cqn.order[] }} */ exports.orderBy = (...args) => ({orderBy: ql.orders(...args)}) exports.orders = (...args) => { const [x] = args; if (x.raw) return cds.parse._select('from X order by',args).orderBy if (is_array(x)) args = x else if (is_object(x) && !x.ref) { return Object.entries(x) .map (each => _ordering_term (...each)) } return args.map (each => { if (each.ref) return each if (typeof each === 'string') return _ordering_term (...each.split(' ')) }) function _ordering_term (ref,sort) { const ot = cds.parse.ref(ref) if (sort) ot.sort = sort == 1 ? 'asc' : sort == -1 ? 'desc' : sort return ot } } /** @returns {{ limit: { rows: { val: any; }; offset?: { val: any; }; }; }} */ exports.limit = (...args) => { const [ limit, offset ] = args; if (limit?.raw) return {limit: cds.parse._select('from X limit',args).limit } if (!offset) return { limit: { rows: { val: limit } } } else return { limit: { rows: { val: +limit }, offset: { val: +offset } } } }; const _cqn_or_val = x => typeof x === 'object' ? x : {val:x} const is_object = x => typeof x === 'object' const is_array = Array.isArray /** * Returns a new instance of Query for the given input, * that has the given one as prototype. */ exports.clone = function (q,_) { return Query.prototype.clone.call(q,_) }