Query Notation (CQN)
Introduction
CQN is a canonical plain object representation of CDS queries. Such query objects can be obtained by parsing CQL, by using the query builder APIs, or by simply constructing respective objects directly in your code.
For example, the following three snippets all construct the same query object:
// Parsing CQL tagged template strings
let query = cds.ql `SELECT from Foo`// Query building
let query = SELECT.from (ref`Foo`)// Constructing plain CQN objects
let query = {SELECT:{from:[{ref:['Foo']}]}}Such queries can be executed with cds.run:
let results = await cds.run (query)Following is a detailed specification of the CQN as TypeScript declarations, including all query types and their properties, as well as the fundamental expression types. Find the full CQN type definitions in the appendix below.
SELECT
Following is the TypeScript declaration of SELECT query objects:
class SELECT { SELECT: {
distinct? : true
count? : true
one? : true
from : source
columns? : column[]
where? : xo[]
having? : xo[]
groupBy? : expr[]
orderBy? : order[]
limit? : { rows: val, offset: val }
}}CQL SELECT queries enhance SQL's SELECT statements with these noteworthy additions:
- The
fromclause supports{ref}paths with infix filters. - The
columnsclause supports deeply nested projections. - The
countproperty requests the total count, similar to OData's$count. - The
oneproperty causes a single row object to be read instead of an array.
Also SELECT statements with from as the only mandatory property are allowed, which is equivalent to SQL's SELECT * from ....
.from
source
Property from specifies the source of the query, which can be a table, a view, or a subquery. It is specified with type source as follows:
class SELECT { SELECT: { //...
from : source
}}type source = ref &as | SELECT | {
join : 'inner' | 'left' | 'right'
args : [ source, source ]
on? : expr
}Used in:
SELECT
.columns
column
as
cast
infix
expand
Property columns specifies the columns to be selected, projected, or aggregated, and is specified as an array of columns:
class SELECT { SELECT: { //...
columns : column[]
}}type column = '*' | expr &as &cast | ref &as &(
{ expand?: column[] } |
{ inline?: column[] }
) &infixinterface as { as?: name }
interface cast { cast?: {type:name} }
interface infix {
orderBy? : order[]
where? : expr
limit? : { rows: val, offset: val }
}Used in:
SELECT
.where
.having
.search
Properties where, and having, specify the filter predicates to be applied to the rows selected, or grouped, respectively. Property search is of same kind and is used for full-text search.
class SELECT { SELECT: {
where : xo[]
having : xo[]
search : xo[]
}}.orderBy
order
class SELECT { SELECT: { //...
orderBy : order[]
}}type order = expr & {
sort : 'asc' | 'desc'
nulls : 'first' | 'last'
}Using:
exprUsed in:
SELECT
INSERT
UPSERT
CQN representations for INSERT and UPSERT are essentially identical:
class INSERT { INSERT: UPSERT['UPSERT'] }
class UPSERT { UPSERT: {
into : ref
entries? : data[]
columns? : string[]
values? : scalar[]
rows? : scalar[][]
from? : SELECT
}}interface data { [elm:string]: scalar | data | data[] }Using:
ref,exprscalar,SELECTSee also:
UPDATE.data,
Data to be inserted can be specified in one of the following ways:
- Using
entriesas an array of records with name-value pairs. - Using
valuesas in SQL's values clauses. - Using
rowsas an array of one or morevalues.
The latter two options require a columns property to specify names of columns to be filled with the values in the same order.
.entries
Allows input data to be specified as records with name-value pairs, including deep inserts.
let q = {INSERT:{ into: { ref: ['Books'] }, entries: [
{ ID:201, title:'Wuthering Heights' },
{ ID:271, title:'Catweazle' }
]}}let q = {INSERT:{ into: { ref: ['Authors'] }, entries: [
{ ID:150, name:'Edgar Allan Poe', books: [
{ ID:251, title:'The Raven' },
{ ID:252, title:'Eleonora' }
]}
]}}See definition in INSERT summary
.values
Allows input data to be specified as an single array of values, as in SQL.
let q = {INSERT:{ into: { ref: ['Books'] },
columns: [ 'ID', 'title', 'author_id', 'stock' ],
values: [ 201, 'Wuthering Heights', 101, 12 ]
}}See definition in INSERT summary
.rows
Allows input data for multiple rows to be specified as arrays of values.
let q = {INSERT:{ into: { ref: ['Books'] },
columns: [
'ID', 'title', 'author_id', 'stock'
],
rows: [
[ 201, 'Wuthering Heights', 101, 12 ],
[ 252, 'Eleonora', 150, 234 ]
]
}}See definition in INSERT summary
UPDATE
class UPDATE { UPDATE: {
entity : ref
where? : expr
data : data
with : changes
}}.data
Data to be updated can be specified in property data as records with name-value pairs, same as in INSERT.entries.
interface data { [element:name]: scalar | data | data[] }.with
changes
Property with specifies the changes to be applied to the data, very similar to property data with the difference to also allow expressions as values.
interface changes { [element:name]: scalar | expr | changes | changes[] }DELETE
class DELETE { DELETE: {
from : ref
where? : expr
}}Expressions
expr
ref
val
xpr
list
func
param
xo
name
scalar
Expressions can be entity or element references, query parameters, literal values, lists of all the former, function calls, sub selects, or compound expressions.
type expr = ref | val | xpr | list | func | param | SELECTtype ref = { ref: ( name | { id:name &infix })[] }
type val = { val: scalar }
type xpr = { xpr: xo[] }
type list = { list: expr[] }
type func = { func: string, args: expr[] }
type param = { ref: [ '?' | number | string ], param: true }type xo = expr | keyword | operator
type operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
type keyword = 'in' | 'like' | 'and' | 'or' | 'not'
type scalar = number | string | boolean | null
type name = stringNOTE
CQN by intent does not understand expressions and therefore keywords and operators are just represented as plain strings in flat xo sequences. This allows us to translate to and from any other query languages, including support for native SQL features.
Full cqn.d.ts File
/**
* `INSERT` and `UPSERT` queries are represented by the same internal
* structures. The `UPSERT` keyword is used to indicate that the
* statement should be updated if the targeted data exists.
* The `into` property specifies the target entity.
*
* The data to be inserted or updated can be specified in different ways:
*
* - in the `entries` property as deeply nested records.
* - in the `columns` and `values` properties as in SQL.
* - in the `columns` and `rows` properties, with `rows` being array of `values`.
* - in the `from` property with a `SELECT` query to provide the data to be inserted.
*
* The latter is the equivalent of SQL's `INSERT INTO ... SELECT ...` statements.
*/
export class INSERT { INSERT: UPSERT['UPSERT'] }
export class UPSERT { UPSERT: {
into : ref
entries? : data[]
columns? : string[]
values? : scalar[]
rows? : scalar[][]
from? : SELECT
}}
/**
* `UPDATE` queries are used to capture modifications to existing data.
* They support a `where` clause to specify the rows to be updated,
* and a `with` clause to specify the new values. Alternatively, the
* `data` property can be used to specify updates with plain data only.
*/
export class UPDATE { UPDATE: {
entity : ref
where? : expr
data : data
with : changes
}}
/**
* `DELETE` queries are used to remove data from a target datasource.
* They support a `where` clause to specify the rows to be deleted.
*/
export class DELETE { DELETE: {
from : ref
where? : expr
}}
/**
* `SELECT` queries are used to retrieve data from a target datasource,
* and very much resemble SQL's `SELECT` statements, with these noteworthy
* additions:
*
* - The `from` clause supports `{ref}` paths with infix filters.
* - The `columns` clause supports deeply nested projections.
* - The `count` property requests the total count, similar to OData's `$count`.
* - The `one` property indicates that only a single record object shall be
* returned instead of an array.
*
* Also, CDS, and hence CQN, supports minimalistic `SELECT` statements with a `from`
* as the only mandatory property, which is equivalent to SQL's `SELECT * from ...`.
*/
export class SELECT { SELECT: {
distinct? : true
count? : true
one? : true
from : source
columns? : column[]
where? : xo[]
having? : xo[]
groupBy? : expr[]
orderBy? : order[]
limit? : { rows: val, offset: val }
}}
type source = OneOf< ref &as | SELECT | {
join : 'inner' | 'left' | 'right'
args : [ source, source ]
on? : expr
}>
type column = OneOf< '*' | expr &as &cast | ref &as & OneOf<(
{ expand?: column[] } |
{ inline?: column[] }
)> &infix >
type order = expr & {
sort : 'asc' | 'desc'
nulls : 'first' | 'last'
}
interface changes { [elm:string]: OneOf< scalar | expr | changes | changes[] >}
interface data { [elm:string]: OneOf< scalar | data | data[] >}
interface as { as?: name }
interface cast { cast?: {type:name} }
interface infix {
orderBy? : order[]
where? : expr
limit? : { rows: val, offset: val }
}
/**
* Expressions can be entity or element references, query parameters,
* literal values, lists of all the former, function calls, sub selects,
* or compound expressions.
*/
export type expr = OneOf< ref | val | xpr | list | func | param | SELECT >
export type ref = { ref: OneOf< name | { id:name &infix } >[] }
export type val = { val: scalar }
export type xpr = { xpr: xo[] }
export type list = { list: expr[] }
export type func = { func: string, args: expr[] }
export type param = { ref: [ '?' | number | string ], param: true }
/**
* This is used in `{xpr}` objects as well as in `SELECT.where` clauses to
* represent compound expressions as flat `xo` sequences.
* Note that CQN by intent does not _understand_ expressions and therefore
* keywords and operators are just represented as plain strings.
* This allows us to translate to and from any other query languages,
* including support for native SQL features.
*/
type xo = OneOf< expr | keyword | operator >
type operator = '=' | '==' | '!=' | '<' | '<=' | '>' | '>='
type keyword = 'in' | 'like' | 'and' | 'or' | 'not'
type scalar = number | string | boolean | null
type name = string
// ---------------------------------------------------------------------------
// maybe coming later...
declare class CREATE { CREATE: {} }
declare class DROP { DROP: {} }
// ---------------------------------------------------------------------------
// internal helpers...
type OneOf<U> = Partial<(U extends any ? (k:U) => void : never) extends (k: infer I) => void ? I : never>