Professional Documents
Culture Documents
SAP HANA Smart Data Integration and SAP HANA Smart Data Quality 2.0 SP03
Document Version: 1.0 – 2018-04-24
1 Introduction. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.1 Prerequisites. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5
1.2 Adapter Functionality. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .5
1.3 Adapter Instances. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.4 Capabilities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .6
Description. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Three Levels of Capabilities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
Design Considerations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .10
Capabilities Test Adapter. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
Key Capabilities. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
1.5 Process Overview. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
4 Adapter Classes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
8 Debugging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
8.1 Import the Launch Configuration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
8.2 Set up the Debug Configuration. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
8.3 Register the Debugging Agent and Adapters in SAP HANA. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .62
10 Additional Information. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
10.1 Data Types. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67
10.2 Trace Logging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
10.3 Exception Handling. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 68
10.4 AdapterFactory Class. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .68
This guide describes how to develop custom adapters to connect to a variety of remote sources from SAP HANA.
Adapters bridge the gap between your source system and the SAP HANA database so you can query the remote
system's data from SAP HANA. The adapter instances browse and import metadata as SAP HANA virtual tables.
With virtual tables, you can perform simple or complex SQL queries as you would with a SAP HANA native table.
You can also create real-time adapters that capture changed data.
Consider the following example. A machine sensor is connected to the Internet and provides two URLs to read
data:
● https://machine1/lastevents displays a list of all events that occurred in the last 24 hours
● https://machine1/eventstream displays new events, which are continually added
The goal is to make this data available in SAP HANA by writing a custom adapter. This adapter will be a Java
program that extends some of the classes of the adapter SDK and acts as an interface between the machine
sensor and SAP HANA. To determine which methods to implement, consider the following questions:
● What parameters are required to connect to the sensor? In this example, it could be the URL, a user name, and
user password.
● How should the data be presented in one or more tables? If the sensor returns the data in lines and each line
contains a timestamp, an event type, and event information, then those components suggest the table
structure.
● What happens when in SAP HANA you in execute the command SELECT * from machine-sensor-table?
The adapter needs to connect to the source URL, retrieve the data, and reformat the lines into rows of the
table.
● What happens when in SAP HANA you in execute the command CREATE REMOTE SUBSCRIPTION … using
(SELECT * from machine-sensor-table) targettable hanatable? It will connect to the event
stream URL and keep waiting for new lines indefinitely. Whenever a new line is received, it is parsed, converted
into a row and streamed to SAP HANA.
Related Information
Prerequisites [page 5]
Adapter Functionality [page 5]
Adapter Instances [page 6]
Capabilities [page 6]
Process Overview [page 15]
These knowledge and software prerequisites apply when creating custom adapters for SAP HANA.
Knowledge:
Software:
Refer to the SAP HANA smart data integration Product Availability Matrix (PAM) for currently supported software
versions and other information.
● Java
To check your Java version, open a terminal or command prompt and type java -version.
● SAP HANA studio or Eclipse
● Installation of the Data Provisioning Agent
Related Information
SAP HANA Smart Data Integration and all its patches Product Availability Matrix (PAM) for SAP HANA SDI 1.0
SAP HANA Smart Data Integration and all its patches Product Availability Matrix (PAM) for SAP HANA SDI 2.0
Adapters for SAP HANA smart data integration include these key functionalities.
Functionality Description
Adapter parameter configuration When deploying an adapter, some parameters might be required. These parameters are
configured by an administrator using the Data Provisioning Agent Configuration tool.
Remote source connection Using SAP HANA studio, Web-based Development Workbench, or the SQL command
CREATE REMOTE SOURCE, you configure the adapter with all the parameters required
to connect to the source system. You specify the parameters, hierarchy, and type. These
values are stored in SAP HANA and passed to the adapter at run time.
Remote source browsing Expanding the adapter's remote source node displays the available tables including any
relevant hierarchy.
Virtual tables You make remote tables available for querying by adding them as virtual tables using ei
ther SAP HANA studio or Web-based Development Workbench. the CREATE VIRTUAL
TABLE command. A virtual table is like a database view but it points to a remote table and
contains all the metadata required for SAP HANA, descriptions, columns and their data
types, primary key information, and adapter-specific attributes.
SQL query execution When executing a SQL statement that involves a virtual table, the Data Federation Layer of
SAP HANA analyzes the statement and creates a SQL statement the adapter can inter
pret. Everything beyond the adapter capabilities executes subsequently in SAP HANA. You
can control the supported operations using capabilities on adapter, table, and column lev
els.
Realtime processing In addition to querying tables, adapters can provide the data in real time using a remote
subscription.
The Data Provisioning Server in SAP HANA gets the information about SAP HANA sessions and requests new
adapter instances from the Data Provisioning Agent.
For example, when you execute the first query against a table served by this adapter, a new adapter instance is
created. Other users executing commands in parallel would have their own adapter instances. When the session
terminates, the adapter instance closes. This makes writing adapters convenient because you implement the
adapter from the perspective of a single user, single command only. All real-time commands are yet another
adapter instance, but in this case there is one adapter instance for all real-time requests.
1.4 Capabilities
Source capabilities define functionality such as INNER JOIN or SUBSTRING operations. The SAP HANA optimizer
uses these capabilities to determine whether a SQL operation can be pushed down to the remote source, which
improves performance.
For adapter development, generally the more operations that execute in the source system, the better the
performance. For example, if a table has one million rows and the user selects just one, then it is best to push the
WHERE clause down to the adapter, and the adapter returns the requested row. The alternative for the optimizer
would be to return all rows and filter them in SAP HANA. If all adapters supported the full SAP HANA syntax, it
would be a burden to adapter developers. If the source is a simple database, then it might be possible to translate
each SAP HANA SQL command into source-specific SQL. But that would be a lot of work with all the various SQL
syntax options, and some databases might lack a function or a set operation or simply behave differently.
For the complete list of capabilities supported by this adapter SDK, see the Javadoc, which is installed with the
Data Provisioning Agent (default location is C:\usr\sap\dataprovagent\doc\javadoc). See the
com.sap.hana.dp.adapter.sdk.AdapterConstants class and the ENUMs for AdapterCapabilities, TableCapabilities,
and ColumnCapabilities.
Related Information
Description [page 7]
Three Levels of Capabilities [page 9]
Design Considerations [page 10]
Capabilities Test Adapter [page 11]
Key Capabilities [page 12]
1.4.1 Description
The method getCapabilities is invoked when registering or refreshing the adapter (CREATE ADAPTER and
ALTER ADAPTER REFRESH SQL commands). This method returns the set of available capabilities for the adapter.
When creating a virtual table, the method importMetadata returns a TableMetadata object. One of the
property sets of TableMetadata are the table-level capabilities, which are set by the method
TableMetadata.setCapabilities. Each table column object supports capabilities as well, which are set by the
method Column.setCapabilities.
For example, CAP_SELECT means the adapter itself supports selecting data from the tables. Without this
capability, no table can be read no matter what their individual settings are. On the table level,
CAP_TABLE_SELECT means you can select data from this table.
If the adapter has column-level capabilities enabled with CAP_COLUMN_CAP and the table does as well via
CAP_TABLE_COLUMN_CAP, then the capabilities of each column are evaluated. In such a scenario, an important
column capability would be CAP_COLUMN_SELECT; otherwise the column cannot be used in a SELECT clause.
This approach lets you control filter-related capabilities granularly. You can define the type of filters (CAP_WHERE,
CAP_SIMPLE_EXPR_IN_WHERE), or you can define supported comparisons (CAP_LIKE, CAP_IN,
CAP_NONEQUAL_COMPARISON, and so on) on the adapter level and some on the column level.
Example
The adapter supports WHERE column = 99 but not any other condition (WHERE column > 99 is not
supported).
Another important capability is projection (CAP_PROJECT), which indicates that the columns can be selected
individually. Without this capability, all columns would be read from the adapter and deleted by the Data Federation
layer of SAP HANA. If a typical table of the adapter is very wide but only subsets of the columns are frequently
required, consider enabling projection. The BaseAdapterClass supports projections.
If possible, CAP_LIMIT should be present as well, enabling SQL commands in the form of SELECT top 100 *
from …. The BaseAdapterClass implements that by calling the getNext function only up to the specified
number of times.
Whenever custom data should persist in the table metadata using setAttribute, set the capability
CAP_METADATA_ATTRIBUTE.
There are many more capabilities such as whether the adapter supports ORDER BY, GROUP BY, joins, push-down
of functions in projection or in the WHERE clause, and so on.
Sample Code
CAP_SIMPLE_EXPR_IN_PROJ
CAP_NESTED_FUNC_IN_PROJ
CAP_NONEQUAL_COMPARISON
CAP_NONEQUAL_COMPARISON
CAP_NONEQUAL_COMPARISON
CAP_AND
CAP_NONEQUAL_COMPARISON
CAP_AND_DIFFERENT_COLUMNS
CAP_DIST_AGGREGATES, CAP_EXPR_IN_PROJ
● Adapter-level capabilities are specific to the adapter. For example, Oracle supports full outer join, SAP ERP
supports left outer join, and so on.
● Table-level capabilities apply to a particular table. For example, an SFSF USER table supports reading, but not
loading.
● Column-level capabilities apply to a particular column of a table. For example, the SFSF USER.PASSWORD
column does not support reading its value.
For SQL functions like SUBSTR(), adapter support is available only on the adapter level. But an adapter might
provide different kinds of tables or columns, some of which only support one set of functions. Generally speaking,
adapter capabilities control everything in as much detail as possible. On table and column levels, you can enable or
disable specific options.
Note that adapters might support different capabilities depending on the source version. For example, PostgreSQL
version 7.2 does not support a function, but version 9.0 does. Therefore, all capability-related calls provide the
version string from the remote source. For most adapters, this will be empty.
Concepts and limitations to consider when configuring capabilities for remote source adapters.
Push-down of SQL operations happens via the smart data access logic in the SAP HANA optimizer. When a SQL
statement executes in SAP HANA, if a virtual table is included, the optimizer rewrites the SAP HANA SQL into two.
One executes via the adapter according to its capabilities, and the remaining logic executes in SAP HANA.
This rewrite happens even in a simple SELECT * FROM <virtual_table> command, which will not be sent to
the adapter.
For example:
SELECT id, name FROM v_customer WHERE name between ‘A’ and ‘Z’;
● CAP_SELECT: Yes, select statements are supported against that virtual table
● CAP_PROJECT: supports selecting individual columns
● CAP_WHERE: support filters in general
● CAP_BETWEEN: support the between clause
If the adapter does not support CAP_BETWEEN, then the inner SQL cannot contain the BETWEEN clause:
The expectation might be that both versions return the exact same data. What if the name was in lowercase or
included Unicode characters? SAP HANA does all string comparisons on a binary level; therefore, the BETWEEN
clause executed in SAP HANA would return ‘Bill’ and ‘Todd’ but not ‘frank’ or ‘Ätna’. Other systems might compare
on a lexicographical level without case sensitivity and therefore return all four values. It is not desirable for two
logically identical SQL commands to return different results. Therefore, ensure you set capabilities carefully.
Limitations
The assumption of capabilities is that they are static (set once by the adapter then never changed again). This
ensures the capability information is current in the SAP HANA data dictionary tables. For example, there is a
bitmap mask of all set adapter capabilities in the SYS.ADAPTER_CAPABILITIES_. CAPABILITIES column.
In addition, the optimizer has a few more places to cache the execution plans and such. If table or column
capabilities are used, the information is part of the SAP HANA virtual table. Therefore, you should drop and
recreate the virtual tables as well. You must create a new SQL session to see the changes. For example, in SAP
HANA studio, select the database connection in the top tool palette of the SQL window.
To help you understand adapter capabilities, a capabilities test adapter has been provided and is available in
github: hana-native-adapters/CapabilitiesTestAdapter. This adapter has two tables: the SQLTEXT table
and the ADAPTERCAPS table. You enable and disable individual adapter capabilities using a statement such as:
Then refresh the SAP HANA dictionary using a command such as:
If a table or column capability changed, drop and re-create the virtual table:
Trigger a reconnect to SAP HANA for the session-related caching. To see what is pushed down now, the Explain
Plan feature helps:
The results indicate the SQL commands that were sent to the remote adapter:
Note
The capability settings of this provided sample adapter are kept in static memory, so whenever you restart the
adapter, it clears the user settings and starts with the default settings.
Also keep in mind that the ADAPTERCAPS table returns the capabilities set in the adapter at the moment, not the
SAP HANA data dictionary information and what is actively used by the optimizer.
● AdapterCapability.CAP_SELECT
● AdapterCapability.CAP_UPDATE
● AdapterCapability.CAP_SIMPLE_EXPR_IN_WHERE
Global
The most important adapter capabilities define what kind of SQL commands the adapter supports in general and
other global information.
SELECT-related Capabilities
● CAP_PROJECT: If this capability is disabled, all columns in the exact order of the virtual table are returned. If
the table is wide and it is typical to select only a subset of the columns, then enable this capability when
returning the rows and the columns need to be returned in the order as requested by the SELECT clause.
● CAP_LIMIT: If a SQL statement like SELECT top 100 * FROM <table> is used frequently, the adapter
should support this capability.
Filters
Filters require the CAP_WHERE capability. Pushing down WHERE clauses is the most important feature of most
adapters. However, not all sources support everything. To provide control, the following capabilities are typically
used.
● CAP_NONEQUAL_COMPARISON: When set, all other comparisons are also enabled (greater than, less than,
greater-equal, not-equal, and so on)
● CAP_EXPR_IN_WHERE: Allows for expressions in WHERE clauses
● CAP_IN: The adapter supports the IN operator
● CAP_LIKE: The adapter supports the LIKE operator; must support the characters % and _
● CAP_BETWEEN: The adapter supports using the BETWEEN clause in SAP HANA. Note: Even if AND and
NON_EQUAL are not enabled, the SQL received by the adapter will resemble col >= 0 AND col <= 100
● CAP_AND: Enables ANDing the same columns together
● CAP_AND_DIFFERENT_COLUMNS: Enables ANDing different columns together
● CAP_OR: Enables ORing the same columns together
● CAP_OR_DIFFERENT_COLUMNS: Enables ORing different columns together
Conversion functions
Conversion functions are important for WHERE clauses when implicit data-type conversions occur. For example,
the command SELECT * FROM table WHERE float_column = 3.0 cannot be pushed down unless
to_double() can be pushed down because the optimizer needs to execute SELECT * FROM table WHERE
float_column = to_double(3.0).
● CAP_BI_CAST
● CAP_BI_TO_*
For advanced cases, every SAP HANA function and operation can be pushed down. Depending on where the
function in used (in the projection, in the filter, and so on), you must enable the corresponding capability to allow
for expressions.
Aggregations
Aggregations require CAP_GROUPBY and CAP_EXPR_IN_PROJ. This category consists of the GROUP BY clause,
HAVING clause and the aggregation functions. A statement such as SELECT region, sum(revenue),
count(*) FROM table GROUP BY region requires the capabilities CAP_EXPR_IN_PROJ (otherwise no
functions are allowed), CAP_AGGREGATES for min/max/sum/avg/count/count(distinct column), and
CAP_GROUPBY because of the GROUP BY clause.
● CAP_AGGREGATES
● CAP_COUNT_DIST_MULT_COLS
● CAP_EXPR_IN_GROUPBY
● CAP_HAVING
● CAP_SIMPLE_EXPR_IN_GROUPBY
Joins
Joins require CAP_JOINS. If the global CAP_JOINS is enabled, then inner joins are supported. To support outer
joins, full outer joins, or combinations of inner and outer joins, additional capabilities are available. To support
more than simple a.column = b.column join conditions, expressions can be enabled.
● CAP_JOINS_OUTER
● CAP_JOINS_FULL_OUTER
● CAP_JOINS_MIXED
● CAP_JOINS_OUTER
● CAP_EXPR_IN_FULL_OUTER_JOIN
● CAP_EXPR_IN_INNER_JOIN
● CAP_EXPR_IN_LEFT_OUTER_JOIN
This overview describes the process for creating, deploying, and enabling custom adapters for SAP HANA smart
data integration.
1 Install Data Provisioning Administrator Data Provisioning Agent installer Administration Guide
Agent
2 Install Data Provisioning fea Developer SAP HANA studio Adapter SDK Guide
ture
3 Create plug-in project Developer SAP HANA studio Adapter SDK Guide
6 Export adapter as a deploya Developer SAP HANA studio Adapter SDK Guide
ble plug-in
7 Deploy and register adapter Administrator Data Provisioning Agent Configuration tool Administration Guide
with SAP HANA
8 Enable adapter with a new re Administrator SAP HANA studio Adapter SDK Guide
mote source connection
The second step in deploying a custom adapter is to install the Data Provisioning Feature in SAP HANA studio. The
Data Provisioning Feature provides plug-ins that let you run custom adapters in SAP HANA studio.
Prerequisites
Ensure the Data Provisioning Agent has been installed and is available to the computer where you want to install
the Data Provisioning Feature.
Procedure
Next Steps
Next, begin creating a custom adapter in SAP HANA studio by adding a new plug-in project.
Prerequisites
Procedure
1. In SAP HANA studio, open the Plug-in Development perspective ( Window Open Perspective Other
Plug-in Development ).
2. Right-click in the Package Explorer white space and select New Plug-in Project .
3. Enter a project name such as HelloWorldAdapter.
4. For Target Platform, select an OSGi framework: Equinox and click Next.
5. Optionally change the Execution Environment depending on your adapter code.
6. In the Templates dialog, select Data Provisioning Adapter Wizard and click Finish.
Results
The template configures OSGi configuration requirements, registers the adapter with the framework, and so on.
The Package Explorer view displays the new adapter project including three classes:
● Activator.java
● HelloWorldAdapter.java (Expand this class to view the methods to develop your adapter)
● HelloWorldAdapterFactory.java
As a general rule, if the adapter’s source supports SQL-like syntax, the Adapter or AdapterCDC classes are a good
starting point. For adapters that do not require joins, the BaseAdapterClass is a good starting point.
Adapter is an abstract class for which adapter developers provide the implementation. An adapter object enables
SAP HANA to access remote data sources such as Microsoft SQL Server, Twitter, etc. It performs the following
main operations:
An extension of the Adapter class, the AdapterCDC class, supports real-time messaging
As an example of how to create a real-time adapter by extending AdapterCDC, we will create a simple
HelloWorld adapter. It provides a single table called HELLO. When the Data Provisioning Agent selects from the
table, the adapter returns 10 rows with each containing a row number and a text. The text consists of the user as
specified in the remote connection and another parameter.
This example adapter also supports real time in the sense that every 5 seconds a new text is sent to SAP HANA.
Related Information
In order to connect to the remote source, the adapter usually needs to get some connection parameter values. But
for SAP HANA to know what kind of parameters the user must enter, the adapter is queried.
The Data Provisioning Agent invokes this method when the adapter is created in SAP HANA. It should return a
RemoteSourceDescription object containing one list of normal properties and a list of credential properties,
both stored in SAP HANA in the data dictionary or a secure store.
Example
@Override
public RemoteSourceDescription getRemoteSourceDescription() throws
AdapterException {
RemoteSourceDescription rs = new RemoteSourceDescription();
The Data Provisioning Adapter invokes the following methods to get the list of adapter capabilities, defining what
the adapter can do, and get the source version. These methods will be invoked before or after opening the remote
source.
The version String in the getCapablities call must first be obtained by the getSourceVersion method call.
@Override
public String getSourceVersion(RemoteSourceDescription
remoteSourceDescription) throws AdapterException {
return "0.0.0";
}
For regular SQL select commands and for real-time pushes, the Data Provisioning Agent needs a list of supported
capabilities for the adapter. This list is returned by the getCapabilities or getCDCCapabilities method.
Example
@Override
public Capabilities<AdapterCapability> getCapabilities(String version) throws
AdapterException {
Capabilities<AdapterCapability> caps = new Capabilities<AdapterCapability>();
caps.setCapability(AdapterCapability.CAP_SELECT);
caps.setCapability(AdapterCapability.CAP_TRANSACTIONAL_CDC);
caps.setCapability(AdapterCapability.CAP_METADATA_ATTRIBUTE);
return caps;
}
@Override
public Capabilities<AdapterCapability> getCDCCapabilities(String version) throws
AdapterException {
Capabilities<AdapterCapability> caps = new Capabilities<AdapterCapability>();
caps.setCapability(AdapterCapability.CAP_SELECT);
caps.setCapability(AdapterCapability.CAP_TRANSACTIONAL_CDC);
caps.setCapability(AdapterCapability.CAP_METADATA_ATTRIBUTE);
return caps;
}
There are two types of real-time adapter, one is transactional like Database and the other is non-transactional or
streaming like Twitter, Facebook. For streaming we have a different capability called,
AdapterCapability.CAP_NON_TRANSACTIONAL_CDC
For real-time execution, the adapter must extend the AdapterCDC interface and the getCapabilities method
should return the following at minimum:
Example
@Override
public Capabilities<AdapterCapability> getCapabilities(String version)
throws AdapterException {
Capabilities<AdapterCapability> capability = new
Capabilities<AdapterCapability>();
capability.setCapabilities(capabilities);
return capability;
}
Related Information
Capabilities [page 6]
The open and close methods establish and terminate connections with the remote source.
This method is invoked when the adapter tests the connection for a remote source, or when it browses a remote
source for metadata or reads data.
The adapter must establish a connection to the remote source using the provided connection information and
ideally keep that connection open until the close method is invoked.
Before an adapter executes any commands other than querying the capabilities or
RemoteSourceDescriptions, the open method is called for a new adapter instance. Here, you should validate
the provided parameters, and throw an AdapterException otherwise.
Example
@Override
public void open(RemoteSourceDescription connectionInfo, boolean isCDC) throws
AdapterException {
@SuppressWarnings("unused")
String password = "";
try {
username = new
String(connectionInfo.getCredentialProperties().getCredentialEntry(CREDENTIAL).get
User().getValue(),
"UTF-8");
password = new
String(connectionInfo.getCredentialProperties().getCredentialEntry(CREDENTIAL).get
Password().getValue(),
"UTF-8");
} catch (UnsupportedEncodingException e1) {
name =
connectionInfo.getConnectionProperties().getPropertyEntry("name").getValue();
}
When this method is invoked, the adapter must clean up all the memory, shut down any threads, and terminate
the connection to the remote source.
After it has been closed, this adapter instance can no longer handle transactions unless a new open operation is
performed.
Example
@Override
public void close() throws AdapterException {
// There are no resources or connections to be closed
}
The setFetchSize, setBrowseNodeId, and browseMetadata methods are used for metadata browsing.
The framework executes these methods to perform browsing of a remote source object. The browseMetadata
method may be called multiple times if there are more than fetchSize nodes.
This method establishes the size of the delivery unit. The Data Provisioning Agent reads the
framework.fetchSize parameter from the dpagentconfig.ini file. When the agent invokes this method of
the adapter, the agent passes that value. This method must assign it to an internal variable so it can be referenced
when the getNext and browseMetadata methods are invoked in order to limit the number of records or
metadata objects being returned by those methods.
Example
@Override
public void setFetchSize(int fetchSize) {
this.fetchsize = fetchSize;
}
When you expand a node inside a remote source, the adapter method setBrowseNodeId invokes to define which
element of the hierarchy was expanded. If the remote source itself was expanded, the passed nodeId string is null;
otherwise, it will be the nodeId string of the expanded node returned by the browseMetadata method.
In the example of a database adapter, the list of all users is at the root level. When one of these users is expanded,
the list of that user’s tables displays. Therefore, whenever the nodeId of the setBrowseNodeId is null, the
browseMetadata method returns a list of users and the nodeId is set to the user name. When the
setBrowseNodeId is not null, the nodeId set is a user and hence returns a list of nodes and their nodeId should
be <user>.<tablename> to uniquely define a table.
Note
Do not rely on the hierarchy in SAP HANA studio to open sequentially. Suppose you expand a hierarchy, then
the session timed out. Upon reconnecting, a new adapter instance starts. The first node to be expanded will be
one of the tree, for example <database1.user1>. So don't keep track of the hierarchy levels expanded in the
browseMetadata method. Also, all nodes should be unique and allow reconstructing the full hierarchy path.
Example
@Override
public void setBrowseNodeId(String nodeId) throws AdapterException {
this.currentbrowsenode = nodeId;
}
@Override
public List<BrowseNode> browseMetadata() throws AdapterException {
if (this.currentbrowsenode == null) {
List<BrowseNode> nodes = new ArrayList<BrowseNode>();
// list should have at max this.fetchsize elements
// okay, in this case it is just one always
BrowseNode node = new BrowseNode(HELLO, HELLO);
node.setImportable(true);
node.setExpandable(false);
nodes.add(node);
return nodes;
} else {
// Well, since there is no hierarchy all non-root nodes return zero
children
return null;
}
}
When SAP HANA creates a new virtual table, the table metadata is required. Your task is therefore to return a
TableMetadata object containing the expected structure in SAP HANA.
Typically this involves specifying a unique table name with which the exact source table can be identified, for
example, nodeId is <database>.<user>.<tablename>. Therefore, TableMetadata should use the same name
as table name so that when querying the virtual table, the adapter does not get just a table name but the full
information required: database, user, and table name in one string.
Furthermore for each source column a SAP HANA datatype has to be chosen and other information being set,
primary key information for example.
Source-specific metadata can be attached to the table and each column by using the corresponding
setAttribute() method. This metadata persists in SAP HANA along with the virtual table and can be queried.
Example
@Override
public Metadata importMetadata(String nodeId) throws AdapterException {
if (nodeId.equals(HELLO)) {
List<Column> schema = new ArrayList<Column>();
Column col1 = new Column("ROWNUMBER", DataType.INTEGER);
schema.add(col1);
Column col2 = new Column("TEXT", DataType.NVARCHAR, 80);
schema.add(col2);
TableMetadata table = new TableMetadata();
table.setName(nodeId);
table.setColumns(schema);
return table;
} else {
throw new AdapterException("No remote table of this name");
}
}
Custom parameters
Some adapters (Twitter, SOAP, ABAP) support the ability to pass custom parameters (user-specified override
parameters) to various input sources and output sources. You can create such adapters by allowing an optional
parameter list to be specified for a virtual table input or output source within the flowgraph XML and for virtual
tables in a Replication Task.
Type Description
CDC_READER_OPTIONS Reader options for input source that supports change data
capture (CDC)
CDC_LOADER_OPTIONS Loader options for output source that supports change data
capture (CDC)
At design time, during the call to the importMetadata() method, you can set these options as shown in this
example.
During execution, you can then extract the supplied values from the user interface.
These properties are stored and queried using the following statement: SELECT * FROM
"PUBLIC"."VIRTUAL_TABLE_PROPERTIES"
if (info.getTableOptions().getPropertyGroup("Public_Stream") != null) {
propGroup = info.getTableOptions().getPropertyGroup("Public_Stream");
String track = propGroup.getPropertyEntry("track").getValue();
String follow = propGroup.getPropertyEntry("follow").getValue();
}
Select * from <Virtual_Table_Name> WITH DATA PROVISIONING PARAMETERS ('
<PropertyGroup name="__DP_CDC_READER_OPTIONS__">
<PropertyGroup name="Public_Stream">
<PropertyEntry name="track ">Hello World</PropertyEntry>
<PropertyEntry name="follow ">11223344, 22334455</PropertyEntry>
</PropertyGroup>
</PropertyGroup>');
You can see these values reflected in the following XML sample.
To read data from the source, SAP HANA sends a SQL string to the adapter and then continues fetching sets of
rows until no more are found.
The query execution starts with an executeStatement call that passes the SQL constructed by the Data
Federation Layer into the adapter along with generic information about the statement.
The first task is to break down the provided SQL string into its components. For support, the
ExpressionParserUtil.buildQuery method is provided. This call returns a query object that can then be
further analyzed regarding the select part, joins, filters, and other components.
Note
With the capabilities on the adapter, table, and potentially on the column level, the kind of SQL produced by the
Data Federation Layer is controlled. If, for example, the filter capability had been turned off entirely, no SQL
string passed in would ever contain a WHERE clause, thus making the parsing much simpler.
Example
@Override
public void executeStatement(String sql,StatementInfo info) throws
AdapterException {
Query query = null;
List<ExpressionParserMessage> messageList = new
ArrayList<ExpressionParserMessage>();
query = (Query) ExpressionParserUtil.buildQuery(sql, messageList);
if(query == null)
throw new AdapterException("Parsing the sql " + sql + " failed");
ExpressionBase sda_fromclause = query.getFromClause();
if (sda_fromclause instanceof TableReference) {
// Kind of useless, only one single table exists anyhow...
tablename_to_read = ((TableReference) sda_fromclause).getUnquotedName();
After the executeStatement method completes, the SgetNext method is called in a loop until no additional
rows return.
Example
@Override
public void getNext(AdapterRowSet rows) throws AdapterException {
if (tablename_to_read.equals(HELLO)) {
int batchsize = 0;
while (rowsread<10 && batchsize<this.fetchsize) {
AdapterRow row = rows.newRow();
/*
* Actually we need to check what columns had been selected.
* But since the AdapterCapability.CAP_PROJECT has not been set
* we know for sure the table structure: it is the HELLO table with
the columns
* ROWNUMBER of type Integer
* TEXT of typo nvarchar(80)
*/
row.setColumnValue(0, rowsread);
row.setColumnValue(1, username + " said: Hello " + name);
rowsread++;
batchsize++;
}
} else {
// cannot happen anyhow
}
}
Adapters can not only be queried but can push messages to SAP HANA in real time as well. The SAP HANA user
executes the command:
Example
create remote subscription <name> using (select * from <virtual_table> where ...)
target [table | task | procedure] <targetname>;
When this subscription becomes active, the adapter will be informed and must send changes from the source
immediately. These changes are sent as a stream of rows in real time.
Related Information
Whenever an adapter restarts and there were active subscriptions, the adapter must identify the records that have
already been processed by SAP HANA for each subscription. This depends on the type of source options that are
available, controlled by the methods requireDurableMessaging and supportsRecovery.
If this method returns true, the stream of data is received and when a transaction is committed, all the rows with
the commit's transaction ID are written into SAP HANA and committed there. When the adapter restarts, the Data
Provisioning Server calls the start method, and its SubscriptionSpecification.getCommittedID
parameter contains the transaction ID for the data the server would like to get again. It is the adapter's
responsibility now to send the data starting from this past transaction ID again.
When the supportsRecovery method returns false because the source does not allow reading of past data, the
Data Provisioning Server must persist the sent data. The details of this are controlled by the method:
requireDurableMessaging.
@Override
public boolean supportsRecovery() {
return false;
}
If recovery is not supported, and requireDurableMessaging returns true, all data sent is persisted in the Data
Provisioning Server. For example, if there is a failure during Seq5, the adapter does not need to send Seq1 to Seq4
again. Data that was sent successfully is never lost.
If the method returns false, the Data Provisioning Server can avoid the overhead of storing the intermediate data
in a queue, but the adapter needs to be able to send transactions again. The logic would be that the adapter sends
the data, the Data Provisioning Server waits until the commit is received, and then all changed rows of the
transaction are inserted into SAP HANA. Once the data is committed in SAP HANA, the adapter commitChange
method is invoked and therefore the adapter comprehends that the transaction was safely stored and will not be
requested again. Rows not committed or the commitChange was not received the adapter will need to send again.
Example
@Override
public boolean requireDurableMessaging() {
return true;
}
@Override
public void committedChange(SubscriptionSpecification spec) throws
AdapterException { }
When a user requests a new real-time subscription, that definition is first added to the adapter by calling the
addSubscription() method. The provided SubscriptionSpecification object contains all information
about the definition, e.g. the select statement of the requested real-time data.
At this point the adapter must do everything required to prepare for the real-time capture of the source but should
not activate it yet.
Example
@Override
If the source supports subscribing to changes, like databases or Salesforce, you can use addSubscription to
subscribe to that source and get an ID which you return in this method. If the source does not support subscribing,
this method should just return null.
This is the reverse operation to addSubscription. The subscription was stopped already and should be removed
entirely.
Example
@Override
public void removeSubscription(SubscriptionSpecification spec) throws
AdapterException {
// we do not deal with new subscriptions here but in stop
}
When a subscription should start to collect changes in the source and send them to SAP HANA, the start method
is called. When a subscription is reset or removed, the stop method is called.
The activation of real-time data capture should be done when the start method is called. The adapter gets the
ReceiverConnection object and the SubscriptionSpecification object and thus has everything necessary
to start collecting the changes in the source and send rows via the ReceiverConnection parameter.
Note
The ReceiverConnection is the same for all subscriptions; it is not subscription-specific.
The start method gets the SubscriptionSpecification parameter, which contains all the information about
the requested data. Therefore, the typical procedure is:
1. Remember the ReceiverConnection parameter; it will be needed when sending the data.
2. Parse the received SQL.
Example
@Override
public void start(ReceiverConnection conn, SubscriptionSpecification spec) throws
AdapterException {
// remember the connection. If it was set already it does not matter, the
connection is the same for all
this.receiverconnection = conn;
// add the new spec to the list of all currently active specs
this.activespecs.put(spec.getSubscription(), spec);
// Parse the spec's SQL Statement so we know what table to read from.
// Granted, there is just one table but its the principles
Query query = null;
List<ExpressionParserMessage> messageList = new
ArrayList<ExpressionParserMessage>();
query = (Query) ExpressionParserUtil.buildQuery(spec.getSQLStatement(),
messageList);
if(query == null)
throw new AdapterException("Parsing the sql " + spec.getSQLStatement() +
" failed");
ExpressionBase fromclause = query.getFromClause();
String tablename = null;
if (fromclause instanceof TableReference) {
// Kind of useless, only one single table exists anyhow...
tablename = unquote(((TableReference) fromclause).getName());
} else {
throw new AdapterException("select does not read a single table???");
}
// We need the table metadata, actually the selected columns from the Query.
// But since the adapter does not support projections, that is the same
anyhow.
TableMetadata m = (TableMetadata) importMetadata(tablename);
this.activespeccolumns.put(spec.getSubscription(), m.getColumns());
// This adapter does something every 5 seconds, we need a thread for that.
// But one thread is enough for all subscriptions.
if (poller == null) {
poller = new Poller();
poller.start();
}
}
This is the inverse operation to start, so it should undo everything start did. It tells the source that changes are no
longer requested for this subscription.
@Override
public void stop(SubscriptionSpecification spec) throws AdapterException {
this.activespecs.remove(spec.getSubscription());
this.activespeccolumns.remove(spec.getSubscription());
}
The real-time activation commands in SAP HANA occur in four phases: create the remote subscription, alter its
queue, perform the initial load, and mark the end of that load.
There is one peculiar issue in real time: A typical real-time scenario is to copy all existing rows from the source to
the target and then start the real-time capture. But that approach has a timing issue. If the capture starts after the
initial load completes, all the changed rows during the initial load could be lost. If the real-time capture starts
before the initial load, the initial load might find rows already and produce an error with a primary key violation or,
worse, update the recently changed record with the initial values.
To solve that problem and not force customers to switch their source system into a read-only mode while the initial
load runs, the real-time activation commands in SAP HANA run in four phases.
1. Create the remote subscription: This defines the remote subscription, the SQL requested, what the target is,
and so on. All of this is validated, but aside from that, nothing happens. No information is sent to the adapter.
2. Alter the remote subscription queue: This is when the adapter’s addSubscription and start methods are
called. The addSubscription is called only if this is a new subscription, not one that was previously stopped.
Therefore the adapter starts collecting changes and sends them to the Data Provisioning Server. The
subsequent rows are not applied to the target table; they are queued. Then the adapter’s beginMarker
method is called, and it should add a changed row of type BeginMarker to the stream of data.(This separation
is required for transaction log reader adapters only. Regular adapters just send the begin marker as requested
and nothing else.)
3. Execute the initial load: Now there is enough time to execute the initial load by copying the entire data set
from the source to the target, for example by executing an INSERT...SELECT from the virtual table. Because
the changed data is still queued, no data is written into the target table yet.
4. Now that the initial load is finished, add an EndMarker row: The user executes the SQL ALTER REMOTE
SUBSCRIPTION DISTRIBUTE command. With this command, the adapter’s endMarker method is called,
and the method must add an EndMarker row into the stream of data. When the Data Provisioning Server
receives this row, it knows that the initial load has been completed, and it can start distributing all future
changed rows into the SAP HANA target.
Example
@Override
public void beginMarker(String markername, SubscriptionSpecification spec)
throws AdapterException {
if (receiverconnection != null) {
AdapterCDCRowSet rowset =
AdapterAdmin.createBeginMarkerRowSet(markername);
rowset.getCDCRow(0).setSeqID(
new SequenceId(System.currentTimeMillis()));
receiverconnection.sendRowSet(rowset);
} else
throw new AdapterException(
"begin marker requested for a non-active subscription???");
}
@Override
public void endMarker(String markername, SubscriptionSpecification spec)
throws AdapterException {
if (receiverconnection != null) {
AdapterCDCRowSet rowset =
AdapterAdmin.createEndMarkerRowSet(markername);
rowset.getCDCRow(0).setSeqID(
new SequenceId(System.currentTimeMillis()));
receiverconnection.sendRowSet(rowset);
The final step is to implement the code that sends real-time changed rows to SAP HANA.
The adapter is now running and waiting for changed rows in the source. The real-time environment is
transactional: when changed rows are sent to SAP HANA, they will be loaded, but not committed. Therefore, they
do not display unless the adapter sends a commit row command.
Note
There are two kinds of adapters, one that is transactional and another where each row is considered as one
transaction.
In order to send rows, the adapter should use the ReceiverConnection from the start method. By calling its
sendRowSet(AdapterCDCRowSet) method, all rows of that AdapterCDCRowSet are sent to SAP HANA.
Hence, the task of the adapter is to retrieve changed rows, create an AdapterCDCRowSet object, and this object
has to contain all changed rows for all subscriptions plus a commit row.
Dealing with multiple subscriptions can be challenging. If, for example, subscription1 requested col1, col2 where
col1=1, and subscription2 requested col1, col5 without a filter. The adapter needs to capture the changes of col1,
col2, and col5 for all rows in the source. And for each changed row, create one rowset – with the requested
columns – for subscription1 with those rows that met the filter criteria and a second rowset for col1, col5 and all
changed rows – no filter had been requested.
Or, using CDCCapabilities, you can determine what kind of SQL can be pushed down into the adapter and let
SAP HANA do the rest.
Example
@Override
public void run() {
int rowcount = 0;
while (isInterrupted() == false) {
try {
TimeUnit.SECONDS.sleep( 5 );
} catch (InterruptedException e) {
interrupt();
}
if (receiverconnection != null && activespecs.size() != 0) {
ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.putLong(System.currentTimeMillis());
byte[] transactionid = buffer.array();
SequenceId sequence = new SequenceId(System.currentTimeMillis());
try {
for (SubscriptionSpecification spec : activespecs.values()) {
List<Column> columns =
activespeccolumns.get(spec.getSubscription());
receiverconnection.sendRowSet(AdapterAdmin.createCommitTransactionRowSet(new
SequenceId(System.currentTimeMillis()),
transactionid));
} catch (AdapterException e) {
// data was not been sent
}
}
}
}
}
For example:
Example
You implemented an adapter that reads all the operations on a database table. It includes INSERT,
BEFORE_IMAGE, AFTER_IMAGE, and DELETE rows. The database supports TRUNCATE statements to enable
deleting all the contents of a table. Therefore, a TRUNCATE row with no column values set would be sent to SAP
HANA. Or, the table might be partitioned and the partition with REGION=’US’ should be truncated. Then a
TRUNCATE row with the REGION column containing the value US should be sent to SAP HANA.
Example
The source system indicates that sales order 1234 has changed. You don't know what changed; all you see are
line items 2 and 3 of the sales order. Therefore, in the target you must delete all rows of this sales order and
insert the current source rows again: DELETE WHERE salesorder=1234 and INSERT line items 2 and 3. In
other words, a TRUNCATE row with the SALESORDER column set to 1234 would be followed by all the current
line items using REPLACE.
When a changed record gets pushed to a Data Provisioning Adapter, the data for all subscribed columns are sent.
A possible exception to this behavior is when one of the columns is a large object (BLOB, CLOB, or NCLOB data
type). These columns sometimes contain too much data to be sent in a single call. If so, the large-object data won't
be sent with the changed record.
When receiving a changed record, the adapter must check the data type of each column to determine if it’s a large
object type and verify whether data was sent for those columns. If there are one or more large object columns
without data, the adapter must call the Row.setColumnLobIdValue(int columnIndex, long lobId) or
Row.setColumnLobIdValue(int columnIndex, long logId, LobCharset charSet) method to notify
the Data Provisioning Server that the column is a LOB and that its data needs to be fetched from the remote
source. The lobId value being passed to the Row.setColumnLobIdValue() method must identify the column and the
row for which data needs to be retrieved from the remote source. The Data Provisioning Server will provide this
value later when invoking the getLob() method of the adapter. If the large object column is NCLOB, you must use
the Row.setColumnLobIdValue(int columnIndex, long logId, LobCharset charSet) method
because it also indicates the encoding applied to the data, which is required for internal processing.
When the changed record is sent to the Data Provisioning Server using the
ReceiverConnection.sendRowSet(RowSet) method, the server determines whether there are any large-
object columns for which data must be retrieved from the remote source. If there are, the adapter getLob(long
lobId, byte[] bytes, int bufferSize) method will be invoked as many times as necessary to get all of
the data. The lobId parameter provided in the getLob() method is the same as what the adapter provided when
calling Row.setColumnLobIdValue(int columnIndex, long lobId) or
Enterprise Semantic Services lets business users identify a data's source by providing an array of information
about the object.
Procedure Description
GET_REMOTE_SOURCE_OBJECTS_TREE When a user navigates into the browsing hierarchy of the re
mote source to select objects/containers to publish, GET_RE
MOTE_SOURCE_OBJECTS_TREE isthe only way to get access
to the container of an object in the hierarchy.
GET_REMOTE_SOURCE_TABLE_DEFINITIONS Get the common table metadata (the information the adapter
already has in its TableMetadata object). Provides a light
weight procedure to get legacy metadata.
GET_REMOTE_SOURCE_OBJECTS_LIST Get a flat list of all remote objects with their unique names.
Adapters can support Enterprise Semantic Services by calling these procedures. For example, by calling
GET_REMOTE_SOURCE_TABLE_DEFINITIONS with a defined remote source name and remote table name(s), you
can get detailed information about tables, columns, descriptions, primary keys, foreign keys, and so on.
These procedures can also provide information not available in regular tables like table properties, column
properties, and Enterprise Semantic Services definitions. Therefore, as an adapter developer, it is useful to provide
as much information as possible about the tables, for example using importMetadata(). These procedures let
you query a remote source to retrieve all table metadata subset elements at once rather than importing each one
as a virtual object.
Related Information
Get the common table metadata (the information the adapter already has in its TableMetadata object).
Input Parameters
Output Parameters
● TABLE
● VIEW
● PROCEDURE
● OTHER: To capture objects that are not mappable/
importable in SAP HANA. For example, SYNONYM
in SAP HANA is considered importable, but when
creating a virtual table on it, it fails.
DEFAULT_LANGUAGE VARCHAR(2) The default locale for the description; can be NULL.
IS_INSERTABLE VARCHAR(5)
IS_UPDATABLE VARCHAR(5)
IS_DELETEABLE VARCHAR(5)
IS_UPSERTABLE VARCHAR(5)
IS_SELECTABLE VARCHAR(5)
IS_REMOTE_SUBSCRIP VARCHAR(5)
TION_SUPPORTED
IS_REMOTE_SUBSCRIP VARCHAR(5)
TION_TRANSACTIONAL
Get a flat list of all remote objects with their unique names. The remote object unique name can be used to get
more details using subsequent calls to other procedures.
Input Parameters
FILTER_DATABASE NVARCHAR(256) Optional filter on the database. Does not support pat
tern, only equals. Empty string ('') used when no data
base, NULL value used when FILTER_DATABASE should
not be used to narrow the search.
FILTER_PHYSICAL_NAME NVARCHAR(256) Optional filter on the name of the object, supporting pat
tern in the form of owner%. NULL (or empty '') value
used when FILTER_PHYSICAL_NAME should not be
used to narrow the search.
FILTER_UNIQUE_NAME NVARCHAR(5000) Optional filter on the unique name of the object, sup
porting pattern in the form of owner%. NULL (or empty
'') value used when FILTER_UNIQUE_NAME should not
be used to narrow the search.
Output Parameters
● TABLE
● VIEW
● PROCEDURE
● OTHER--To capture objects that are not mappable/
importable in SAP HANA. For example, SYNONYM
in SAP HANA is considered importable, but when
creating a virtual table on it, it fails.
DEFAULT_LANGUAGE VARCHAR(2) The default locale for the description; can be NULL.
IS_USER_DEPENDENT VARCHAR(5) An object might return the same data regardless of what
user it is calling, or it depends on the user. Example: A
table with row-level security.
LAST_MODIFICATION_TIME TIMESTAMP Optional, indicates the last time the object was modi
STAMP fied.
Input Parameters
Output Parameters
5.8.4 GET_REMOTE_SOURCE_COLUMN_DESCRIPTIONS
Procedure
Input Parameters
Output Parameters
COLUMN_NAME NVARCHAR(256)
The IS_TREE column in the REMOTE_SOURCES system view indicates whether Enterprise Semantic Services
must use the browsing hierarchy API in the prepare publication phase or the ObjectList procedure.
By default, all remote sources previously created have this property set to false (which means the browsing
hierarchy is not flat). If you want your adapter to browse tree-like hierarchies, add the
CAP_SUPPORT_RICH_METADATA_BY_TREE capability to your adapter.
Sample Code
In order to simplify working with the adapter SDK for common adapters, the BaseAdapterClass and its
referenced TableLoader class can be extended.
The most important constraint for adapters of this kind is they do not support push-down of joins and expressions.
For example, SELECT SUBSTRING(col1, 1, 10) FROM <virtualtable> could not be handled by such an
adapter. Instead, SAP HANA would select the col1 from the adapter and perform the substring operation inside
the database.
While the BaseAdapterClass deals with all global operations, for each table (or group of tables), a TableLoader
class (or its simplified version, the TableLoaderSimpleFilter class) is to be used. This is useful in particular for
tables where the table structure is constant. For example a Tweet always has the same columns, hence you could
build a TableLoader for Tweets..
The TableLoaderSimpleFilter class is based on the TableLoader class but analyzes the filter conditions and
arranges them into lists of filters per column. Therefore, the pushed-down filter requires AND conditions between
different columns (no ORs). For each single column, multiple filters are allowed with either ANDs or ORs but not
both. In other words, the logical bracketing is:
Related Information
Reporting the Configuration of the Remote Source Description for BaseAdapterClass [page 44]
Reporting the Capabilities of the Remote Source for BaseAdapterClass [page 45]
Beginning and Ending Communication with the Remote Source for BaseAdapterClass [page 46]
Browsing a Remote Source's Metadata for BaseAdapterClass [page 47]
Preparing to Create Virtual Tables in SAP HANA for BaseAdapterClass [page 47]
Executing Queries for BaseAdapterClass [page 48]
Real-Time Execution for BaseAdapterClass [page 50]
Real-Time Changed Rows for BaseAdapterClass [page 50]
Example
@Override
public void addRemoteSourceDescriptors(PropertyGroup root) throws
AdapterException {
root.addProperty(new PropertyEntry("name", "Hello whom?"));
}
For all elements added to the provided CredentialProperties object, the user provided values will be stored in
a SAP HANA secured area. The helper methods addUserCredential and addPasswordOnlyCredential are
available to simplify that task.
Example
@Override
public void addRemoteSourceCredentialDescriptors(CredentialProperties credential)
throws AdapterException {
addUserCredential(credential, "Credentials", "Credentials", "Username",
"Password");
}
The Data Provisioning Adapter invokes these methods to get the list of adapter capabilities, defining what the
adapter can do, and get the source version. These methods will be invoked before or after opening the remote
source.
As the BaseAdapterClass itself supports projections, real-time transactions and more, it returns a first set of
capabilities already. Typical pushdown scenarios are turned on by overriding the corresponding pushdown*
method and letting it return true. This sets the corresponding capabilities. The default implementation is that each
capability is turned off.
If more flexible control over the capabilities is needed, the get*Capabilities method can return an array of
capabilities to be set for the adapter.
The same approach is taken for table capabilities and column capabilities. The default implementation sets them
to something useful, but can be overridden.
The open and close methods establish and terminate connections with the remote source.
These are identical to those in the AdapterCDC version. Note the use of the getPropertyValueByPath method,
however, which allows access to individual values, easily and securely.
Example
@Override
public void open(RemoteSourceDescription descriptor, boolean cdc) throws
AdapterException {
username = getUsername(descriptor, CREDENTIAL);
@SuppressWarnings("unused")
String password = getPassword(descriptor, CREDENTIAL);
Example
@Override
public void close() throws AdapterException {
// There are no resources or connections to be closed
}
When SAP HANA requests the list of remote tables, the addNodes method is called, and the adapter has to add
either table nodes or group nodes to it.
Unlike when using the AdapterCDC class directly, the BaseAdapterClass constructs the hierarchical node IDs
internally and provides helper functions to create table and group nodes via createNewTableBrowseNode and
createNewGroupNode.
Example
@Override
public void addNodes(List<BrowseNode> nodes) throws AdapterException {
nodes.add(createNewTableBrowseNode(HELLO, HELLO, "Hello World Table"));
}
Whenever SAP HANA creates a new virtual table, the table metadata is required. Therefore, your task is to return a
TableMetadata object containing the expected structure in SAP HANA.
Unlike with the AdapterCDC, this version of the importMetadata method turned the dot separated full path like
<database>.<user>.<table> into an ArrayList with the individual levels. And an empty shell of the
TableMetadata is provided as well. In addition, the TableLoader has static methods to add columns, primary
keys for various datatypes in a convenient way.
Example
Example
@Override
public void importMetadata(ArrayList<String> fullIDStringToLevels, TableMetadata
table) throws AdapterException {
TableLoaderHelloWorldAdapter2.importMetadata(table);
}
To read data from the source, SAP HANA sends a SQL string to the adapter and then continues fetching a set of
rows until no more are found.
While in the AdapterCDC case, the executeStatement method provided the raw SQL only. The version here
parses the SQL and determines which TableLoader class to use.
The executeStatement is present in case some generic operation is required but usually can be left empty.
The executeStatement is invoked with a TableLoader object representing the source table to read from.
Additionally, the code used to read data from the source is triggered here as well.
Example
@Override
protected void executeStatement(TableLoader tableloader) throws AdapterException {
// parsing happens in the BasedAdapterClass already
}
This method must return the proper TableLoader for each table name. Depending on the use case, each table
might have a TableLoader object on its own or a single TableLoader object that deals with all source tables. For
a database, the second implementation makes sense because the table structure is derived from the source. For a
table with a constant structure, for example the HELLO table in the HelloWorld2Adapter, one TableLoader
per table makes sense.
Example
@Override
protected TableLoader getTableLoader(String tableName)
throws AdapterException { if (tableName != null && tableName.equals(HELLO)) {
return new TableLoaderHelloWorldAdapter2(this);
} else {
throw new AdapterException("Unknow table"); } }
The getNextRowData has to return an object that contains all the data itself or that can be used to construct all
data for the current row. If the current row is the last row, then the hasNoMoreRows method is to be set.
Alternatively, when the function returns a null object, it is the indicator that no more data can be found.
Example
@Override
protected Object getNextRowData() throws AdapterException {
if (getRowNumberToRead() < 10) {
// return an object that can be used to parse the return values of the
row.
// anything as long as it is not null
return getRowNumberToRead();
} else {
hasNoMoreRows();
return null;
}
}
The object the getNextRowData returned is sent into the setColumnValue method. Based on the position of the
column in the table metadata, the row's column value has to be set.
Example
@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,
((HelloWorldAdapter2) getAdapter()).username +
" said: Hello " +
((HelloWorldAdapter2) getAdapter()).name);
break;
}
}
@Override
public void executeStatementEnded() throws AdapterException {
// no resources to close
}
As an example, imagine you executed SELECT TEXT, TEXT, ROWNUMBER from HELLO. You requested the
second column of the table twice and the first column of the table as third. Because the index starts with 0, the
setColumnValue will be called with:
Therefore, only the columns actually selected from will be processed, avoiding unnecessary computations on
columns not to be read.
In real-time execution, adding subscriptions, distributing the rows to the correct subscribers, and dealing with the
begin/end marker has been already implemented. All that remains is to implement the code to start and stop a
subscription.
In case the source does not support sending messages, a frequent polling can be done as well. For that, the
method getPollingInterval should return the polling sleep time in seconds. Then the poll method is called
at that frequency.
Example
@Override
protected void startSubscription(SubscriptionRuntimeInformation s) throws
AdapterException {
}
@Override
protected void stopSubscrition(SubscriptionSpecification subscription) {
}
@Override
public int getPollingInterval() {
return 5;
}
To send changed rows, the BaseAdapterClass provides the helper methods addCDCRow, sendRows and
commit.
If getPollingInterval returns a value other than zero, then the pollStart method is called when starting the
first subscription of the adapter and the pollEnd method when the last subscription has been removed. While at
least one subscription is active, the poll method is called every <polling-interval> seconds. The pollStart
method allows adding the logic needed before the polling even starts. Then the poll method is called frequently. If
the last subscription is removed, the pollEnd method is called.
Example
@Override
public void pollStart() throws AdapterException {
counter = 0;
}
@Override
public void poll() {
try {
addCDCRow(HELLO, counter++, RowType.INSERT);
sendRows();
commit();
} catch (AdapterException e) {
e.printStackTrace();
}
Camel Adapter is a framework that allows you to create custom adapters using a few configuration files and Spring
DSL (Domain Specific Language) with little or even no coding efforts. It is based on Apache Camel and Spring
Framework. To use Camel Adapter, you must know Apache Camel and Spring Framework.
Their websites and the versions that the Camel Adapter uses are listed below.
Apache Camel has many built-in components that can be used to access a variety of outside data sources. You can
use them and define a custom adapter to access these supported data sources.
Note
For now, Camel Adapter only supports non-database components, meaning that it only supports Camel
components that access non-database data sources.
Related Information
Defining a new custom adapter starts with adding an Adapter element in <DPAgent_root>/camel/
adapters.xml file. Here is an example.
Example
<Adapters>
...
<Adapter
Related Information
Adapter type, display name, and description are defined as XML attributes of the Adapter element. They are used
to construct an AdapterFactory for the custom adapter.
<Adapter type="CamelFacebookAdapter"
displayName="Camel Facebook Adapter"
description="Camel Facebook Adapter">
Remote source description defines which parameters the custom adapter has. It is defined by
RemoteSourceDescription element. What you define is what Adapter.getRemoteSourceDescription() returns.
<RemoteSourceDescription>
<PropertyGroup
name="configuration"
displayName="Configuration">
<PropertyEntry
name="httpProxyHost"
displayName="HTTP Proxy Host"
description="HTTP Proxy Host" isRequired="false"/>
<PropertyEntry
name="httpProxyPort"
displayName="HTTP Proxy Port"
description="HTTP Proxy Port" isRequired="false"/>
</PropertyGroup>
<CredentialEntry
name="app_credential"
displayName="App Credential"
userDisplayName="App ID"
passwordDisplayName="App Secret"/>
<CredentialEntry
name="user_credential"
displayName="User Credential"
userDisplayName="User ID"
passwordDisplayName="User Access Token"/>
</RemoteSourceDescription>
name name
displayName displayName
description description
Supported PropertyEntry XML attributes and their counterparts in SDK PropertyEntry class are below.
name name
displayName displayName
description description
defaultValue defaultValue
isRequired required
Adapter Capabilities
<Capabilities>
CAP_SELECT,
CAP_BIGINT_BIND
</Capabilities>
@Override
public Capabilities<AdapterCapability> getCapabilities(String version) throws
AdapterException {
List<AdapterCapability> list = new ArrayList<AdapterCapability>();
list.add(AdapterCapability.CAP_SELECT);
list.add(AdapterCapability.CAP_BIGINT_BIND);
Capabilities<AdapterCapability> capabilities = new
Capabilities<AdapterCapability>();
capabilities.setCapabilities(list);
return capabilities;
}
Camel route template defines the processing logic for methods of Adapter using Spring DSL. It must be tied to
adapter definition by the RouteTemplate XML element. For example:
<RouteTemplate>facebook.xml</RouteTemplate>
Related Information
The Camel route template is a Spring configuration file. Camel Adapter provides a template file (found in
<DPAgent_root>/camel/route-template.xml) with which you can begin to create the route template. Copy
this file and rename it to a new route template file name for your adapter (for example, facebook.xml).
Comments inside this file will help guide you through steps of adding content.
Remote source parameters are defined within RemoteSourceDescription element of Adapter configuration in
adapters.xml. In the route template XML file, you can use the Spring style placeholder $
{<remote_source_parameter_name>} in Spring bean configuration, or use the Camel style placeholder
{{<remote_source_parameter_name>}} within the <camelContext> tag to represent the remote source
parameter values. Note that you cannot use the Spring style placeholder $
<PropertyEntry name="httpProxyHost"
displayName="HTTP Proxy Host"
description="HTTP Proxy Host"
isRequired="false"/>
For credential entry, you must use the placeholder ${<credential_name>_user} and $
{<credential_name>_password} to represent the values of the user and password properties of the credential
entry. (<credential_name> represents the name attribute of the credential entry). For example, for the following
credential entry, you can use ${app_credential_user} and ${app_credential_password} to represent the values of
App ID and App Secret.
<CredentialEntry name="app_credential"
displayName="Credential"
userDisplayName="App ID"
passwordDisplayName="App Secret"/>
For the above examples, if the remote source parameters are used within <camelContext> tag, they must be
represented using the Camel style placeholders {{<httpProxyHost>}}, {{app_credential_user}}, and
{{app_credential_password}}.
The core of the route template is a Camel route that is defined to process a variaty of Adapter SDA functionalities.
At runtime, the custom adapter instance transforms the calls on Adapter SDA methods to Camel messages, and
then passes them to the Camel route to process.
A Camel message header DpCommand represents which operation needs to be processed for the message. Its
value can be one of the four values: browse, import, query, update.
The messages with DpCommand header “browse” and “import” correspond to the calls on
Adapter.browseMetadata and Adapter.importMetadata, respectively. Camel Adapter provides a component
MetadataComponent to allow you to specify a metadata XML file in which all tables's definitions are configured.
<when>
<simple>${header.DpCommand} == 'browse' or ${header.DpCommand} == 'import'</
simple>
<setHeader headerName="DpMetadataBrowser">
<simple>config</simple>
</setHeader>
<setHeader headerName="DpMetadataConfigFile">
<simple>my-metadata.xml</simple>
</setHeader>
<to uri="dpmetadata:config"/>
</when>
The message with DpCommand header “query” requests a SQL query. The message body is a SQL SELECT
statement.
<when>
<!-- A query command, that is, the in-message body is a SQL SELECT statement. --
>
<simple>${header.DpCommand} == 'query'</simple>
<!-- Convert the SELECT statement to a structural SQL bean. -->
<bean ref="sqlBean" method="toSQLBean"/>
<!--
Process the query command. The result must be a collection of JavaBeans or Maps
in the in-message body. The collection must be "iterable" - that is, it must
implement java.util.Iterator or java.util.Iterable.
If the result is a collection of JavaBeans, each JavaBean represents a
table record. The names of JavaBean properties must exactly match the column
names.
And subsequently 'DpResultType' header must be set to 'bean'.
If the result is a collection of Maps, each Map object represents a table
record.
The keys of Map must represent the column names, and the values are column
values.
And subsequently 'DpResultType' header must be set to 'map'.
-->
<!--
Set 'DpResultType' header to 'bean' if the result is a collection of JavaBeans.
Set 'DpResultType' header to 'map' if the result is a collection of Maps. -->
<setHeader headerName="DpResultType"><simple>bean</simple></setHeader>
<to uri="dpresult:dpresult"/>
</when>
You can convert the SELECT statement to a structural SQL bean using a Camel bean that Camel Adapter provides.
From the SQL bean, you can get all parts of the SQL statement.
WhereColumn
The SELECT statement must be processed by configuring Camel processors and/or endpoints. The result must be
a collection of JavaBeans or Maps, and is set as the message body before it flows into the 'dpresult:dpresult'
endpoint. The following must be noted.
● If the result is a collection of JavaBeans, each JavaBean is corresponding a data row in the target table. Each
JavaBean property must have same name as the column name in the target table.
● If the result is a collection of Maps, each Map object is corresponding a data row in the target table. In the map,
keys must be the column names of the target table.
● The result must be a collection of JavaBeans or Maps. Here, the collection object could be an array, a Java
collection class that implements java.util.Iterator, a Java class that implements java.util.Iterable, or even a
single JavaBean or Map that represents only one row result.
The message with DpCommand header 'update' requests a SQL INSERT/UPDATE/DELETE operation. The
message body is the SQL statement. The concrete SQL statement type could be consulted via the SQL bean's
dmlType property. You can convert the SQL statement using SQL Bean. Note that the SQL statement could be a
prepared statement. If the SQL statement is a prepared statement, there will be a DpIsPreparedStatement header
with 'true' value. And the values of the prepared statement will be represent as a map and set to the
DpPreparedStatementValues header. The SQL statement must be processed by configuring Camel processors
and/or endpoints. Before it flows into the 'dpresult:dpresult' endpoint, you must specify how many rows are
inserted/updated/deleted via the DpUpdateCount header.
<when>
<!-- An update command, that is, the in-message body is a SQL INSERT/UPDATE/
DELETE statement. -->
<simple>${header.DpCommand} == 'update'</simple>
<!-- Convert the SELECT statement to a structural SQL bean. -->
<bean ref="sqlBean" method="toSQLBean"/>
<!-- Process the update command. The update command is an INSERT/UPDATE/DELETE
statement.
You can use SQLBean's dmlType property to determine the concrete SQL statement
type. E.g.
<choice>
<when>
<simple>${body.dmlType} == 'INSERT'</simple>
... </when>
Once you complete the route template configuration, you need to determine which dependent jar files are needed
at runtime. These jars are mainly Camel component jars and their dependencies. They must be put into
<DPAgent_root>/camel/lib directory to be accessible to Camel Adapter. Before putting your dependent jars
into the lib directory, make sure that they are not provided by Data Provisioning Agent in <DPAgent_root>/
plugins directory. If any jars already exist in <DPAgent_root>/plugins directory, you don't need to put them
into <DPAgent_root>/camel/lib directory.
To set up debugging for a custom adapter, first import the launch configuration, then set up the debug
configuration with the appropriate bundles.
Then you can test your adapter by adding break points and running the adapter.
Related Information
To enable debugging for a custom adapter, first import the Data Provisioning Agent launch configuration.
Procedure
1. Import the agent launch configuration by selecting File Import Run/Debug Launch Configurations .
2. Click Next and browse to the Data Provisioning Agent installation and open the ui folder (for example
<DPAgent_root>\ui).
3. Select the ui folder and click OK.
4. In the Import Launch Configurations window, select the check box for the doc folder, and in the right pane
select the check box for AgentConfig.launch.
5. Click Finish.
Results
The configuration is available under the OSGi Framework node (in SAP HANA studio, from the Debug Agent Config
menu, select Debug Configurations).
To debug a custom adapter, after importing the launch configuration, set up the debug configuration.
Procedure
○ com.sap.hana.dp.agent
○ com.sap.hana.dp.adapterframework
○ com.sap.hana.dp.log4jfragment
5. On the Arguments tab, verify the following arguments are present:
○ Program arguments:
-os ${target.os} -ws ${target.ws} -arch ${target.arch} -nl ${target.nl} -
consoleLog –console
○ VM arguments:
Declipse.ignoreApp=true -Dosgi.noShutdown=true
Next Steps
Once the adapter is running inside the OSGi console, next register the debug agent and adapters in SAP HANA.
Data Provisioning Agents and adapters must be registered before they can be used.
Context
In a production environment, administrators use the Data Provisioning Agent Configuration tool. However, in a
development environment, you typically don't have access to this tool. Therefore, you can register the agent and
its adapters in one of two ways:
If not already registered, this command registers the agent (invokes the CREATE AGENT SQL statement on
SAP HANA) and any installed adapters (invokes the CREATE ADAPTER SQL statement on SAP HANA).
● Individually invoke the following SQL statements:
Note
Host and port are only applicable if the protocol is TCP.
After developing and debugging a custom adapter, you then deploy it to the Data Provisioning Agent and register it
with SAP HANA.
The process for deploying a custom adapter for SAP HANA is as follows:
Related Information
To make a custom adapter available to SAP HANA, first export it as a deployable plug-in.
Procedure
1. In SAP HANA studio, ensure the Eclipse Plug-in Development Environment is installed. If not, install it as
follows:
Using the Data Provisioning Agent Configuration tool, deploy the custom adapter (.jar) to the Data Provisioning
Agent, and register the adapter with the SAP HANA server.
After developing a custom adapter, deploy it to the Data Provisioning Agent and register it with the SAP HANA
server.
Context
Refer to the Administration Guide for SAP HANA Smart Data Integration and SAP HANA Smart Data Quality for
details on the following steps.
Procedure
Next Steps
Enable the adapter in SAP HANA studio (create a new remote source and import tables).
To use a custom adapter in SAP HANA studio, you create a new remote source, select the adapter and agent, and
configure the connection.
Context
Procedure
Next Steps
Import tables per the SAP HANA Administration Guide: 6.1.1.2 Creating Virtual Tables from Remote Objects.
This section includes supplementary information regarding creating custom adapters for SAP HANA.
Related Information
Remote source data types map to the following custom adapter data types.
Also refer to the Data Types section of the SAP HANA SQL and System Views Reference for more information on
how these data types map to your sources.
Related Information
Java adapters use log4j. The logs are in the OSGI console and framework.log file. The log file is located in the log
folder relative to the Data Provisioning Agent executable.
To log use:
logger.info(“Started”);
You can configure the log level using the framework.log.level property in dpagentconfig.ini. Note this will
require a restart of the Data Provisioning Agent.
framework.log.level=ALL
Java provides the AdapterException class that lets you throw an exception in case of failure. The
AdapterException is a checked exception, which allows you to throw the exception with just text or with text and
ID. When the ID is provided in the exception, it will be sent as a string to the server.
The AdapterException also provides a needReconnect option that is useful when you lose the connection to
the source system and you want the server to call open again. In that scenario, you can throw an
AdapterException with this flag set to true, then the server will clean up the old session and call open again.
If the adapter requires parameters during deployment, the methods getAdapterConfig and potentially the
validateAdapterConfig methods of the AdapterFactory class can be used to define and later validate the
list of parameters to be entered.
Example
@Override
public RemoteSourceDescription getAdapterConfig() throws AdapterException {
PropertyGroup ui = new PropertyGroup("UI");
PropertyGroup connectionProperties = new
PropertyGroup("config","FileAdapter Configuration");
PropertyEntry root = new PropertyEntry(FileAdapter.ROOTDIR, "Root
Directory", "The absolute root directory, no file can be read from outside");
Hyperlinks
Some links are classified by an icon and/or a mouseover text. These links provide additional information.
About the icons:
● Links with the icon : You are entering a Web site that is not hosted by SAP. By using such links, you agree (unless expressly stated otherwise in your agreements
with SAP) to this:
● The content of the linked-to site is not SAP documentation. You may not infer any product claims against SAP based on this information.
● SAP does not agree or disagree with the content on the linked-to site, nor does SAP warrant the availability and correctness. SAP shall not be liable for any
damages caused by the use of such content unless damages have been caused by SAP's gross negligence or willful misconduct.
● Links with the icon : You are leaving the documentation for that particular SAP product or service and are entering a SAP-hosted Web site. By using such links,
you agree that (unless expressly stated otherwise in your agreements with SAP) you may not infer any product claims against SAP based on this information.
Example Code
Any software coding and/or code snippets are examples. They are not for productive use. The example code is only intended to better explain and visualize the syntax and
phrasing rules. SAP does not warrant the correctness and completeness of the example code. SAP shall not be liable for errors or damages caused by the use of example
code unless damages have been caused by SAP's gross negligence or willful misconduct.
Gender-Related Language
We try not to use gender-specific word forms and formulations. As appropriate for context and readability, SAP may use masculine word forms to refer to all genders.