Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
werner_daehn
Active Contributor

This post is part of an entire series

Hana Smart Data Integration - Overview

When looking at the adapter code, there are several dimensions to consider:

  • The hierarchy of classes
  • The Adapter Capabilities
  • The call hierarchy
  • The relationship between user actions and the API being called

Class Hierarchy

The hierarchy of the classes is

  1. public abstract class com.sap.hana.dp.adapter.sdk.Adapter
  2. public abstract class com.sap.hana.dp.adapter.sdk.AdapterCDC extends com.sap.hana.dp.adapter.sdk.Adapter
  3. public abstract class com.sap.hana.dp.adapter.sdk.adapterbase.BaseAdapterClass extends com.sap.hana.dp.adapter.sdk.AdapterCDC

The Adapter class is the bare minimum, to be used when writing an adapter for SDA only, no realtime. If realtime should be supported as well, then the AdapterCDC class has to be the foundation.

Both of these classes are very low level. In order to build an adapter based on those, a lot of common code has to be written.

That is the reason why the BaseAdapterClass was built in top of it, which has default implementations for most tasks, which leaves only the bare minimum to be implemented. Use this class as the foundation whenever the adapter is no database, so it does not support joins, only simple where clauses, no group by.

Hence for database adapters the common starting point will be Adapter oder AdapterCDC, for all other adapters the BaseAdapterClass. Even if the adapter does not support Realtime, the BaseAdapterClass can be used still - see Capabilities.

Capabilities

What the adapter can or cannot do is not controlled by the classes implemented but by returning certain things in the getCapability() method. So even if the AdapterCDC or BaseAdapterClass is being used, the adapter has to tell Hana that is supports realtime by returning a list of capabilities containing the element AdapterCapability.CAP_TRANSACTIONAL_CDC in above method.

The different types of Capabilities are a topic of its own but in general terms, they control what kind of SQLs are supported. If the adapter does not support realtime, Hana will raise an error whenever the command create-remote-subscription is executed using this adapter or a virtual table of it.

see Capabilities - SAP HANA Data Provisioning Adapter SDK - SAP Library

Call Hierarchy

Every Hana sessions opens a new Hana adapter instance. So as seen by Java, an Adapter object is created and its methods being called depending on what SQL statements the user executes.

This can be tested easily by setting breakpoints at every adapter method and starting the adapter in Eclipse in debug mode - this has been discussed in the previous chapters already.

For a better understanding the table below shows the action done by the user and its effect on the adapter using the HelloWorld2Adapter as example, the one that is extending the BaseAdapterClass.

Prep Calls

The first stage is when registering an adapter in Hana. Then the adapter's core metadata is queried and stored in the Hana data dictionary table. Because of that any change in the adapter properties and capabilities require the adapter to be refreshed.

SQLs:

CREATE ADAPTER "HelloWorldAdapter2" AT LOCATION AGENT "mylaptop";

ALTER ADAPTER "HelloWorldAdapter2" REFRESH AT LOCATION AGENT "mylaptop";

Messages exchanged between DPServer and Adapter:

  • GET_CAPABILITIES
  • MDS_REMOTE_SOURCE_DESC

The Remote Source Description is an XML consisting of two kinds of property trees, the regular parameters that are public and credential infomration which has to be stored in the secure store by Hana.

The method addRemoteSourceDescriptors(PropertyGroup root) is called and the adapter adds a tree of nodes to it, which will be turned in parts of the remote source XML internally.

Similar the method addRemoteSourceCredentialDescriptors(CredentialProperties credential), where the adapter adds all the nodes which should end up in the secure store of Hana. So essentially both methods do the same thing, they together form the XML being returned, but since the credential tree has to follow more strict rules, it is a separate call with its own helper methods.

Open and Close

With above information the UI has the knowledge what parameters the user has to supply when creating a concrete remote source connection using the adapter.

These entered values are sent to the Adapter's open() method in form of a RemoteSourceDescriptor and can be used to e.g. connect to the actual source system using the supplied information.

Whenever a sessionis no longer needed, a timeout happens or any other error in the connection between Hana and the Adapter, the corresponding close() method is called,

Browsing

When browsing for tables, e.g. by expanding the created remote source, a session is opened and the addNodes() method being called.

The task of the adapter is to add child nodes which should be rendered in the UI then. These returned nodes can either be tables, like in this example a TableBrowseNode representing a certain table is added, or it can be a group node. Such group node can then be further expanded in the UI. In order to know if the root node or such child node was expanded, the method getParentNodeIDString() returns either NULL (the root node got expanded) or the unique name of a group node. There are some more methods to understand the depth of the current tree and the such.

The Table Metadata

When a create virtual table is issued, either manually or via the UI, the adapter's importMetadata() method is invoked. In the BaseAdapterClass an empty table object is created already and the task of the adapter developer is to provide the table name, the columns with datatypes and the such.

Since there could be multiple tables, the table to import is provided in the first parameter. When the browsing is a flat list of table only, this ArrayList is just one element large and contains the table name itself, the same name as being used in the browsing method above.

But if the browsing is multiple levels, this ArrayList contains the path to the unique object, e.g. rootdirectory, subdirectory1, subdirectory2, filename.

In the BaseAdapterClass all handling of one table or a set of tables of similar kinds is handled in a second class, the TableLoader, for better reading. The example code for the HelloWorldAdapter2 is:

public static void importMetadata(TableMetadata table) {

  table.setName(HelloWorldAdapter2.HELLO);

  table.setPhysicalName(HelloWorldAdapter2.HELLO);

  table.setDescription("Hello World table");

  addColumnInteger(table, "ROWNUMBER", "The row number");

  addColumnNVarchar(table, "TEXT", 80, "The Hello text");

}

The table gets a name and columns using these or similar helper methods.

For a database adapter the corresponding code might be different. One TableLoader is covering all tables of the database and the adapter does read the source database data dictionary in order to return the requested table structure. The adapters main task would be to assign the correct Hana datatypes for each source column datatype.

Execution of a query

When the BaseAdapterClass receives the request to execute a query, this higher level class has implemented the code to break apart the sent SQL text into its components using a SQL Parser the Adapter SDK provides. As the BaseAdapterClass does not support any joins, this class can simplify a lot of things. For example it creates a new TableLoader class for this particular table and provides all information like the table name, the where clause being supplied etc.

The TableLoader has methods to override so it can for example execute code against the source system.

The task of the TableLoader implementation is to return one object in the getNextRowData() which contains all the information of one row. This method is called until it tells that there is no more data available.

This object is then passed into the setColumnValue() method along with the information. The reason for this two staged approach are projections.

Imagine above table, it has the two columns ROWNUMBER and TEXT. The SQL being executed selects just the column ROWNUMBER. Or it might be a statement like

select TEXT, ROWNUMBER, ... from ...

The adapter needs to assign to these three columns the proper source value, do all the conversions and the such.

Therefore the setColumnValue() method is called once for every column of the select(!) statement and it provides the information which column should be set and what its source column is.

@Override

protected void setColumnValue(

    int tablecolumnindex,

    int returncolumnindex,

    AdapterRow row,

    Object o) throws AdapterException {

  switch (tablecolumnindex) {

    case 0:

      row.setColumnValue(returncolumnindex, (Integer) o);

      break;

    case 1:

      row.setColumnValue(returncolumnindex, ... + "Hello" + ...);

      break;

  }

}

For the select TEXT, ROWNUMBER from ... this method would be called three times with the same Object o as input and the the values of the pair would be

returncolumnindextablecolumnindexSource Table Column
01TEXT
10ROWNUMBER

Return column index is a simple counter, 0..first column of the select, 1..second column etc and the tablecolumnindex is the index of the column as being created in the virtual table. The virtual table has at index 0 the column ROWNUMBER, then the TEXT column was added hence got the index 1.

It has to be noted that the SQL statement sent to the adapter is NOT(!) the SQL statement being entered in the query. The federation logic of SDA is inbetween, which tries to optimize the SQL sent and also makes sure the adapter capabilities are honored.

For example this select statement here returns the correct result.

Which might be surprising at first, since nobody implemented the substring function in the adapter. When looking at the explain plan for this statement, it becomes obvious what happens.

The SDA layer rewrote the statement to something like

select text, rownumber, text, substring(text, 1, 1) from

  (select ROWNUMBER, TEXT from v_hello);

The inner part being executed by the adapter, the remaining logic inside Hana. The other point noteworthy is the aliasing of the SQL being sent to the adapter. The from clause reads: from "HELLO" "V_HELLO"

So the SQL provides the name of the virtual table - which the adapter does not care about, but also the name of the remote table the adapter is using.