XS Data Services: A Native CDS Client and Query Builder for XS JavaScript
XS Data Services (XSDS) are a JavaScript library for the XS engine to consume SAP HANA artifacts that have been defined using Core Data Services (CDS), the central data modeling concept of HANA. The XSDS library supports the import of CDS entities and their associations and offers managed and unmanaged manipulation of such instances. With additional features such as transaction handling, lazy navigation, support for custom types and views, and incremental query building XSDS offers a level of convenience that makes writing native HANA applications with XS a breeze.
What are XS Data Services?
Core Data Services (CDS) are a cross-platform solution to define semantically rich data models for SAP HANA applications. In essence, a CDS model defines an application’s relevant set of business entities and their relations while abstracting from the underlying technical storage.
The XS Data Services (XSDS) client library for the XS Engine allows JavaScript applications to consume these CDS artifacts as native JavaScript objects. Instead of writing low-level SQL queries involving database tables and columns, application developers can use XSDS to reason meaningfully about high-level business entities and their relations. XSDS understands the CDS metadata model offers methods for typical instance management operations. Advanced queries are constructed using a fluent query API that follows the CDS Query Language specification as closely as possible.
This mini-series of posts expands upon the introductory coverage of Thomas Jung’s blog article on XSDS for SPS9. Our articles cover the import of CDS entities into native HANA applications, working with managed entity instances in XS JavaScript, and advanced unmanaged queries that will unlock all of HANAs capabilities inside native JavaScript. XSDS comes pre-installed with SAP HANA SPS9 or later, so you don’t have to install or import anything at all.
Importing CDS Entities
Let’s assume that we’re building a bulletin board application that allows users to post short text messages and to reply to other users’ posts. Our CDS data model thus defines two entities, user and post:
namespace sap.hana.democontent.xsds;
context bboard {
entity user {
key uid: Integer not null;
Name: String(63) not null;
Email: String(63);
Created: UTCDateTime;
};
type text {
Text: String(255);
Lang: String(2);
};
entity post {
key pid: Integer not null;
Title: String(63) not null;
Text: text;
Author: association [1] to user;
Parent: association [0..1] to post;
Rating: Integer;
Created: UTCDateTime;
};
};
To use the User and Post entities in our JavaScript application we first have to import them:
$.import(“sap.hana.xs.libs.dbutils”, “xsds”);
var xsds = $.sap.hana.xs.libs.dbutils.xsds;
var Post = XSDS.$importEntity(“sap.hana.democontent.xsds”, “bboard.post”);
var User = XSDS.$importEntity(“sap.hana.democontent.xsds”, “bboard.user”);
The first two lines import the XSDS library itself, and the last two lines define the User and Post entities, respectively. $importEntity returns a constructor function that is used as a handle for retrieving and creating entity instances. For most data models this is all that is required to define an entity.
Advanced options allow you to override certain properties of the columns to import. In the simplest case, you could rename a table column to be used as object property or override the primary key definitions:
var Post = XSDS.$importEntity(“sap.hana.democontent.xsds”, “bboard.post”, {
UserID: { $column: “uid” },
Name: { $key: true }
});
You may also provide an existing HDB sequence to generate keys automatically for you. By using a sequence you will not have to populate the key column for newly created instances yourself.
var User = XSDS.$importEntity(“sap.hana.democontent.xsds”, “bboard.user”, {
uid: { $key: “\”SAP_HANA_DEMO\”.\”sap.hana.democontent.xsds::bb_keyid\”” }
});
Note that XSDS does not apply any additional logic to key generation. It is left to the application and the sequence to generate valid, unique keys for your entities.
Associations
One of the key benefits of CDS is the explicit definition of associations between entities. XSDS will read association information automatically and integrate associations as references into the entity definition.
To introduce one-to-one associations manually, e.g., for legacy models not converted to CDS yet, you may provide an $association property that specifies the associated entity type and the foreign key that links your parent entity to the associated entity:
var Post = XSDS.$importEntity(“sap.hana.democontent.xsds”, “bboard.post”, {
Parent: {
$association: {
$entity: “sap.hana.democontent.xsds::bboard.post”,
},
pid: {
$column: “Reference”,
$none: 0
}
}
});
The entity provided by $entity may be the name of the entity of the actual entity constructor itself. Entities referenced by name are loaded automatically with default options if no matching $importEntity()call can be found.
If the association is a one-to-zero-or-one association, the foreign key should define a specific “no association” value with the $none property. The $none values will be used to populate the table columns if the parent entity is persisted without associated entity. In the example above, a value of 0 in the Reference column indicates that the post is not in response to another post.
Note that the “no association” value is different from NULL! A value of $none indicates no association, whereas a value of NULL indicates that the status of the association is unknown. If no $none value is specified, however, XSDS will assume that $none: undefined in order to be consistent with the current de-facto behavior of CDS.
One-to-Many Associations
CDS defines one-to-many associations by using backlinks from the targets to the source. Assume for our example that each post may have any number of unique comments, where each comment contains a reference to its parent post:
entity comment {
key cid: Integer not null;
Text: text;
Author: association [1] to user;
Parent: association [1] to post; // as backlink
Created: UTCDateTime;
};
Since HANA SPS9 does not support the creation of CDS backlinks yet we will have to add the backlink definition manually to our import of post:
var Post = XSDS.$importEntity(“sap.hana.democontent.xsds”, “bboard.post”, {
Comments: {
$association: {
$entity: “sap.hana.democontent.xsds::bboard.comment”,
$viaBacklink: “Parent”
}
}
});
This is equivalent to importing the following, still unsupported CDS definition:
entity post {
key pid: Integer not null;
…
Comments: association [0..*] to comment via backlink Parent;
};
Comments will appear as a native read-only array-like JavaScript object in our instances of the Post entity:
var post = Post.$get({ pid: 69 });
for (var i = 0; i < post.Comments.length; ++i) {
if (post.Comments[i].Author.Name === “Alice”) { … }
}
We’ll see how to work with these array-like objects further down below.
Many-to-Many Associations
CDS defines many-to-many associations by using linking entities that connect source and target instances. Assume for our example that any number of tags may be attached to our posts:
entity tag {
key tid: Integer not null;
Name: String(63);
};
The actual linkage data is stored in a separate table that corresponds to a (potentially keyless) entity:
@nokey entity tags_by_post {
lid: Integer;
Tag: association to tag;
Post: association to post;
};
Again, as HANA SPS9 does not support linking entities, we will have to enrich our Post data model in our import manually:
var Post = XSDS.$importEntity(“sap.hana.democontent.xsds”, “bboard.post”, {
Tags: {
$association: {
$entity: “sap.hana.democontent.xsds::bboard.tag”,
$viaEntity: “sap.hana.democontent.xsds::bboard.tags_by_post”,
$source: “post”,
$target: “tag”,
}
}
});
The $source and $target properties indicate the direction of the association. This import statement is equivalent to importing the following unsupported CDS definition:
entity post {
key pid: Integer not null;
…
Tags: association [0..*] to ds_comments via entity tags_by_post;
};
The tags will appear as a native JavaScript array in our instances of the Post entity:
var post = Post.$get({ pid: 69 });
var tag = Tag.$find({ Text: “cool” });
if (post.Tags.indexOf(tag) < 0) {
…
}
While the representation of tags is similar to that of our comments before, their semantics are quite different. In particular, attaching a new tag to a post would not remove it from other posts this tag may be attached to.
Unmanaged Associations
The most general form of associations supported by CDS are unmanaged associations that define an arbitrary JOIN … ON condition:
var PostWithTopReplies = XSDS.$importEntity(“sap.hana.democontent.xsds”, “bboard.post”, {
TopReplies: {
$association: {
$entity: “sap.hana.democontent.xsds::bboard.post”,
$on: “$SOURCE.\”TopReplies\”.\”Parent\”.\”pid\” = $SOURCE.\”pid\” AND ” +
“$SOURCE.\”TopReplies\”.\”Rating\” >= 3″
}
}
});
Make sure to quote all field names in the $on expression. The syntax of the $on condition is preliminary and likely to change for the next release.
Associated instances are included in a read-only array-like object. Due to the unmanaged nature of these associations the contents of the array cannot reflect unsaved changes to the relation and must be refreshed manually.
XSDS Beyond CDS
The XSDS library imports existing CDS entities, but it can also transform entity definitions and derive new entities. If we want to add a new association not included in the CDS data model, we can derive a new entity in XSDS:
var PostWithComments = Post.$deriveEntity({
Comments: {
$association: {
$entity: “sap.hana.democontent.xsds::bboard.user”,
$viaBacklink: “Post”
}
}
});
This extensibility also allows us to work with legacy SQL data models, where we enrich the data model to make implicit associations explicit:
var SqlPost = XSDS.$defineEntity(“SqlPost”, “\”SOME_TABLE\””, {
Author: {
$association: { $entity: User },
uid: { $column: “Author” }
}
});
Note that we’re using $defineEntity instead of $importEntity here — $defineEntity is another method for defining entities in your application that is specialized on creating data models for plain SQL-based tables without CDS metadata. XSDS will import every table column as an entity property, but as plain SQL lacks the metadata about associations, we need to supply this information ourselves.
Outlook
With our entity imports in place we can now use the constructor functions to work with individual instances or to build advanced queries. Stay tuned for the next installment of our XSDS mini-series!
Excellent blog post Ralph!
I wanted to use the new library immeaditely but it seems like that it is not avaliable yet on the hana cloud platform trial environment.
Do you know when this will be available?
Hello Christian,
Yes, unfortunately the trial environment hasn't received the latest HANA release just yet. There should be an update in July or August, though.
Regards
Ralph
Hi Ralph
Do i put the import part in an XS Javascript file?
Kind regards
Hello Eli,
Sorry for the tardy reply.
Yes, just place the imports into a .xsjs file. There are two parts to the import -- first the XSDS library itself:
$.import("sap.hana.xs.libs.dbutils", "xsds");
var xsds = $.sap.hana.xs.libs.dbutils.xsds;
And then the entities, based on your data model:
var Post = XSDS.$importEntity("sap.hana.democontent.xsds", "bboard.post");
var User = XSDS.$importEntity("sap.hana.democontent.xsds", "bboard.user");
To improve performance you could also pre-generate the result of those $importEntity statements using the xsds_gen script. Then you'd replace the second part of the imports with $.import statements.
$.import("your.path", "post");
var Post = your.path.post.entity;
Just generate a sample file and look at the output -- it should be fairly clear how to use the result.
Hope that helps
Ralph
Hi Ralph,
thank you for providing such an helpful blog series about XSDS!
But after using it a bit I have still two open questions:
1. Is it somehow possible to import entities that don't have a primary key defined on HANA DB Level (yes that's possible in HANA) ? I am getting an error: "No key defined".
2. Related to the problem in Question 1: I tried to use the suggested syntax from your blog post to set a key on xsjs level:
var Test = XSDS.$importEntity(<path>, <entity>, {
...
RunName.Id: { $key: true } // Setting the attribute as primary key
});
Now I am running in the next issue: Since it is also possible in Hana to include [.]s in column names (e.g.'RunName.Id') I cannot address them in JavaScript. Do you have an idea how to address columns with dots?
Thank you for your help!
Regards,
Lukas
Hello Lukas,
Glad you like it!
Regarding your question (1), yes, it is possible to define your own keys, just as you do. XSDS always requires some (unique!) keys to tell instances apart. If your HANA table doesn't have keys you define your own keys in the import statement.
It's also possible to import models without key by specifying $unmanaged: true in the import options, but then you won't be able to $get instances. You can, however, use $query for unmanaged entities, so using $unmanaged: true does make sense in some situations.
Regarding (2), that's not possible unfortunately. CDS simply doesn't allow for dots in component names, as the dot is reserved for entity navigation. HANA may support it, but it's best not to start with this practice at all, even if you're not using CDS right now.
Hope that helps,
Ralph
Thanks for your quick response!
I will try the unmanaged mode to avoid the key definition since my key fields have dots ...
Regarding the dots in column names: I am against this dot-practice in column names, too. But they are coming from the associations between CDS entities: e.g.
entity TestEntity {
Id : association [1] to TestEntity2 {Id};
};
-> that results in Id.Id as column name
Those dots aren't avoidable, are they?
Regards,
Lukas
Oh, then it's entirely different! If you have a CDS model, you shouldn't be looking at the table columns at all.
In your example, you don't reference "{ id.id: 1 }", but "{ id: { id: 1 } }". That's why you cannot have dots in names, as CDS/XSDS would always assume that you have a nested structure here.
So dots in column names are fine if they originate from CDS, but you should use (nested) entity field properties instead of column names.
Ralph
That explains most of it 🙂 and it works with importing.
However, with your syntax, I am still not able to create a new instance via new:
var Test = XSDS.$importEntity(<path>, <entity>, {
Id: { Id: { $key: true }}
}); // Works fine!
var test= new Test({
Id: {
Id: 1
},
...
});
Neither can I $get an entity:
vartest = Test.$get({
Id : {
Id : 1
}});
Do I miss here something?
Lukas
I think the example requires XSDS being written in capital letters:
I have to add that double quotes in the code are not JavaScript-compliant.
I know that this post is quite old, and the documentation is not perfect. But I’d like to add some findings that I have just made, so other readers will not struggle with the ORM-concept.
Update #1
I think it makes sense to use const instead of var when you import packages/libraries. Unless you plan to change the reference to the imported library:
If you want to use xsds_gen (the package that can generate entity classes for you) you should use this statement (it is not documented elsewhere):
Update #2
Let us assume you have the file something.hdbdd in the package com.sap.test.something. You probably has this syntax:
Keep in mind that the xsds_gen library can generate you the text output (this is important) using the function:
By the way, this library is not documented in the JSDoc.
For my particular example, you have to use the following syntax:
Keep in mind that the context name has to be mentioned. Otherwise you will get the error:
This happens, because the table has the name com.sap.test.something::someContext.MyEntry instead of com.sap.test.something::MyEntry.
As a result, you will get JavaScript block that you can copy-paste into your package. Don’t forget to refresh the generated file, if you change your CDS-entity.
p.s. I hope Google will index my comment, so you can save your time and say “thanks” to me.
By the way, I think this is not a documented feature and who knows when it might fail you
Update #3
Keep in mind that you cannot register the same entity twice. Otherwise you will get the error:
That is how I use those two libs:
Update #4
There is the JSDoc help where the method $save is defined with the mistake:
The method doesn't return anything. Here is the method's prototype (taken from the corresponding class):
The optional parameter is tx - the object of type .xsds.Transaction. Be careful 🙂 If you do not provide it, the entity will be immediately persisted (reply to Lukas, yes, I am able to persist the object using this simple syntax below):
Update #5
I am tired a little bit, but I hope you will enjoy the comment. This update is related to the supported CDS primitive types. Currently my entity has the field with the following declaration:
Entity MyEntry {
...
success: Boolean;
};
According to CDS Primitive Data Types the field should accept Boolean values such as true/false. However, when you use the code like I use:
In other words, I have to give the string argument instead of Boolean for the Boolean field. Here is the correct syntax:
I am not joking, but you can provide values from the list: "false", "False", "FaLsE", "TRUe", "1" and "0". If you open the source of the library xsds_queries.xsjslib, you will see that there is no type $.db.types.BOOLEAN == 28. Why 28?, this is because it has been generated by XSDS_GEN:
HANA XS is from the cloud. The version is 1.00.112.04.1467296086.
Happy debugging 🙂
Here is an interesting blog that might help you.