In CAP, relationships between entities can be expressed with associations. In digging into how these are represented internally, I found jq yet again to be invaluable in parsing out info from the internal representation of the definitions. Here’s what I did.
In CAP, SAP’s Cloud Application Programming Model, models are defined declaratively in a human readable format known as CDL, which stands for Core Definition Language1.
CDL example
Here’s a simple example, in a file called services.cds taken from the current Hands-on SAP Dev live stream series on back to basics with CAP Node.js:
aspect cuid { key ID: UUID }
service bookshop {
entity Books : cuid {
title: String;
author: Association to Authors;
}
entity Authors : cuid {
name: String;
books: Association to many Books on books.author = $self;
}
}
Yes, there’s a cuid aspect in @sap/cds/common but I didn’t want the entire contents of that package being expressed in the output that follows, as it would make it more difficult to see the essential parts in this short example.
There’s a “to-one” relationship between Books and Authors, and a “to-many” relationship in the back link beween Authors and Books.
For those non-CAP folks wondering where the foreign key field is in the first relationship, it’s constructed automatically as the Books:author association is a managed one.
CSN equivalent
There’s an internal JSON-based representation of such definitions, which lends itself more readily to machine processing. This representation is called Core Schema Notation, or CSN. We can see the CSN for the CDL source above, like this:
cds compile –to csn services.cds
The default target format is in fact csn so –to csn is unnecessary, but it’s nice to express it explicitly here.
What we get is this:
{
“definitions”: {
“cuid”: {
“kind”: “aspect”,
“elements”: {
“ID”: {
“key”: true,
“type”: “cds.UUID”
}
}
},
“bookshop”: {
“kind”: “service”
},
“bookshop.Books”: {
“kind”: “entity”,
“includes”: [
“cuid”
],
“elements”: {
“ID”: {
“key”: true,
“type”: “cds.UUID”
},
“title”: {
“type”: “cds.String”
},
“author”: {
“type”: “cds.Association”,
“target”: “bookshop.Authors”,
“keys”: [
{
“ref”: [
“ID”
]
}
]
}
}
},
“bookshop.Authors”: {
“kind”: “entity”,
“includes”: [
“cuid”
],
“elements”: {
“ID”: {
“key”: true,
“type”: “cds.UUID”
},
“name”: {
“type”: “cds.String”
},
“books”: {
“type”: “cds.Association”,
“cardinality”: {
“max”: “*”
},
“target”: “bookshop.Books”,
“on”: [
{
“ref”: [
“books”,
“author”,
“ID”
]
},
“=”,
{
“ref”: [
“ID”
]
}
]
}
}
}
},
“meta”: {
“creator”: “CDS Compiler v4.6.2”,
“flavor”: “inferred”
},
“$version”: “2.0”
}
Picking out detail with jq
I’m just interested to see how the associations are represented, so wanted to narrow this CSN down to just elements that are of type cds.Association. Being a relatively involved JSON dataset, this was a job for one of my favourite languages, jq.
Here’s what I came up with, in a file called associations:
#!/usr/bin/env -S jq -f
# Lists entities and shows any elements that are associations
def is_entity: .value.kind == “entity”;
def is_association: .value.type == “cds.Association”;
.definitions
| to_entries
| map(
select(is_entity)
| {
(.key):
.value.elements
| with_entries(select(is_association))
}
)
And here’s how I use it:
cds compile –to csn services.cds | ./associations
The output is:
[
{
“bookshop.Books”: {
“author”: {
“type”: “cds.Association”,
“target”: “bookshop.Authors”,
“keys”: [
{
“ref”: [
“ID”
]
}
]
}
}
},
{
“bookshop.Authors”: {
“books”: {
“type”: “cds.Association”,
“cardinality”: {
“max”: “*”
},
“target”: “bookshop.Books”,
“on”: [
{
“ref”: [
“books”,
“author”,
“ID”
]
},
“=”,
{
“ref”: [
“ID”
]
}
]
}
}
}
]
Here are some notes:
I encapsulated the kind / type determination in helper predicate functions (is_entity and is_association), mostly to keep the main code more succinctI love how the to_entries,from_entries,with_entries(f) family of functions2 help out by normalising JSON objects that have dynamic values for property keysBy using the select(is_entity) in the context of such normalisation, I can easily pick out the “enclosing” object that contains the condition I’m looking forIn contrast to the use of to_entries at the outer (entity) level, I used with_entries to achieve the to_entries | map(…) | from_entries pattern that is so useful
And that’s about it! Score one more for the wonderful utility of jq, in today’s world of JSON.
Alternative approach
While I like the is_entity and is_association definitions, one could make the main code even more succinct like this:
def entities: select(.value.kind == “entity”);
def associations: select(.value.type == “cds.Association”);
.definitions
| to_entries
| map(
entities
| {
(.key):
.value.elements | with_entries(associations)
}
)
Which do you prefer?
Footnotes
1: The human readable language we used to define models is commonly referred to as CDS. Capire, the CAP documentation resource, is more precise, calling it CDL, classing CDL, CSN and other CAP little languages such as CQL and CQN as all being under the general CDS umbrella term. As you can see from the bottom of the CSN output, even the compiler refers to it as CDS!
2: For more on this family of functions, see Reshaping data values using jq’s with_entries and Quick conversion of multiple values using with_entries in jq.
Originally published on qmacro.org
In CAP, relationships between entities can be expressed with associations. In digging into how these are represented internally, I found jq yet again to be invaluable in parsing out info from the internal representation of the definitions. Here’s what I did.In CAP, SAP’s Cloud Application Programming Model, models are defined declaratively in a human readable format known as CDL, which stands for Core Definition Language1.CDL exampleHere’s a simple example, in a file called services.cds taken from the current Hands-on SAP Dev live stream series on back to basics with CAP Node.js:aspect cuid { key ID: UUID }
service bookshop {
entity Books : cuid {
title: String;
author: Association to Authors;
}
entity Authors : cuid {
name: String;
books: Association to many Books on books.author = $self;
}
}Yes, there’s a cuid aspect in @sap/cds/common but I didn’t want the entire contents of that package being expressed in the output that follows, as it would make it more difficult to see the essential parts in this short example.There’s a “to-one” relationship between Books and Authors, and a “to-many” relationship in the back link beween Authors and Books.For those non-CAP folks wondering where the foreign key field is in the first relationship, it’s constructed automatically as the Books:author association is a managed one.CSN equivalentThere’s an internal JSON-based representation of such definitions, which lends itself more readily to machine processing. This representation is called Core Schema Notation, or CSN. We can see the CSN for the CDL source above, like this:cds compile –to csn services.cdsThe default target format is in fact csn so –to csn is unnecessary, but it’s nice to express it explicitly here.What we get is this:{
“definitions”: {
“cuid”: {
“kind”: “aspect”,
“elements”: {
“ID”: {
“key”: true,
“type”: “cds.UUID”
}
}
},
“bookshop”: {
“kind”: “service”
},
“bookshop.Books”: {
“kind”: “entity”,
“includes”: [
“cuid”
],
“elements”: {
“ID”: {
“key”: true,
“type”: “cds.UUID”
},
“title”: {
“type”: “cds.String”
},
“author”: {
“type”: “cds.Association”,
“target”: “bookshop.Authors”,
“keys”: [
{
“ref”: [
“ID”
]
}
]
}
}
},
“bookshop.Authors”: {
“kind”: “entity”,
“includes”: [
“cuid”
],
“elements”: {
“ID”: {
“key”: true,
“type”: “cds.UUID”
},
“name”: {
“type”: “cds.String”
},
“books”: {
“type”: “cds.Association”,
“cardinality”: {
“max”: “*”
},
“target”: “bookshop.Books”,
“on”: [
{
“ref”: [
“books”,
“author”,
“ID”
]
},
“=”,
{
“ref”: [
“ID”
]
}
]
}
}
}
},
“meta”: {
“creator”: “CDS Compiler v4.6.2”,
“flavor”: “inferred”
},
“$version”: “2.0”
}Picking out detail with jqI’m just interested to see how the associations are represented, so wanted to narrow this CSN down to just elements that are of type cds.Association. Being a relatively involved JSON dataset, this was a job for one of my favourite languages, jq.Here’s what I came up with, in a file called associations:#!/usr/bin/env -S jq -f
# Lists entities and shows any elements that are associations
def is_entity: .value.kind == “entity”;
def is_association: .value.type == “cds.Association”;
.definitions
| to_entries
| map(
select(is_entity)
| {
(.key):
.value.elements
| with_entries(select(is_association))
}
)And here’s how I use it:cds compile –to csn services.cds | ./associationsThe output is:[
{
“bookshop.Books”: {
“author”: {
“type”: “cds.Association”,
“target”: “bookshop.Authors”,
“keys”: [
{
“ref”: [
“ID”
]
}
]
}
}
},
{
“bookshop.Authors”: {
“books”: {
“type”: “cds.Association”,
“cardinality”: {
“max”: “*”
},
“target”: “bookshop.Books”,
“on”: [
{
“ref”: [
“books”,
“author”,
“ID”
]
},
“=”,
{
“ref”: [
“ID”
]
}
]
}
}
}
]Here are some notes:I encapsulated the kind / type determination in helper predicate functions (is_entity and is_association), mostly to keep the main code more succinctI love how the to_entries,from_entries,with_entries(f) family of functions2 help out by normalising JSON objects that have dynamic values for property keysBy using the select(is_entity) in the context of such normalisation, I can easily pick out the “enclosing” object that contains the condition I’m looking forIn contrast to the use of to_entries at the outer (entity) level, I used with_entries to achieve the to_entries | map(…) | from_entries pattern that is so usefulAnd that’s about it! Score one more for the wonderful utility of jq, in today’s world of JSON.Alternative approachWhile I like the is_entity and is_association definitions, one could make the main code even more succinct like this:def entities: select(.value.kind == “entity”);
def associations: select(.value.type == “cds.Association”);
.definitions
| to_entries
| map(
entities
| {
(.key):
.value.elements | with_entries(associations)
}
)Which do you prefer?Footnotes1: The human readable language we used to define models is commonly referred to as CDS. Capire, the CAP documentation resource, is more precise, calling it CDL, classing CDL, CSN and other CAP little languages such as CQL and CQN as all being under the general CDS umbrella term. As you can see from the bottom of the CSN output, even the compiler refers to it as CDS!2: For more on this family of functions, see Reshaping data values using jq’s with_entries and Quick conversion of multiple values using with_entries in jq. Originally published on qmacro.org Read More Application Development Blog Posts articles
#SAP
+ There are no comments
Add yours