Blog do projektu Open Source JavaHotel

czwartek, 31 grudnia 2015

New version of JavaHotel application

Introduction
The new version of JavaHotel application has been deployed. Demo version (U/P user/user), full source code is available here. A new option is a journal, a register of daily activities of the user. The journal gives the manager an insight into daily business and workload of all users.
Journal
Journal is available from upper menu: Administrator->Journal

A subset of user action is recorded, the actions related to changes in the status of database and reservation or some customer actions like sending a confirmation note or registering a payment.
Filtering
Also, a convenient journal filtering is implemented. Activities can be filtered on users and activity type.


Future

  • Graphic presentation of journal
  • More granular reporting related to the time of the day, answering the question: "at what time of the day some type of action is more frequent ?"

niedziela, 29 listopada 2015

Power BI, Java and IBM InfoSphere Streams

Introduction
Power BI is part of Microsoft Azure package enabling real-time analytics and visualization. Power BI can be fed by data from multiple sources. Very interesting feature is REST API interface, thereby allowing integration with any tool or application that supports REST calls.
IBM InfoSphere Streams
Although Streams does not contain any direct support for REST interface, it can be easily implemented by com.ibm.streamsx.inet toolkit.
Authentication using com.ibm.streamsx.inet toolkit
To start working with Power BI REST API, we have to authenticate and receive authentication token used later for other REST API calls. Authentication can be done through HTTPPost operator.

use com.ibm.streamsx.inet.http::HTTPPost ;
use com.ibm.streamsx.inet.http::* ;
use com.ibm.streamsx.inet.http::HTTPGetStream ;

boolean responseOk(HTTPResponse r)
{
 return(r.errorMessage == "OK" && r.responseCode == 200) ;
}

composite BiTest
{
 type
  INPUT = tuple<rstring username, rstring password, rstring client_id,
   rstring grant_type, rstring resource> ;
 graph
  (stream<HTTPResponse> HTTPPost_1_out0) as HTTPPost_1 = HTTPPost(Custom_2_out0
   as InputStreamName0)
  {
   param
    url : "https://login.windows.net/common/oauth2/token" ;
  }

  (stream<INPUT> Custom_2_out0) as Custom_2 = Custom()
  {
   logic
    onProcess :
    {
     println("Submit") ;
     submit({ username = "szczodry@szczerbek.onmicrosoft.com", password =
      "Szczuja123", client_id = "22dfcddc-d5b4-4e8a-8358-1658fabdad0b",
      grant_type = "password", resource =
      "https://analysis.windows.net/powerbi/api" }, Custom_2_out0) ;
     println("Done") ;
    }

  }

  (stream<rstring token> Custom_3_out0) as Custom_3 = Custom(HTTPPost_1_out0 as
   S)
  {
   logic
    onTuple S :
    {
     println(S) ;
     if(! responseOk(S))
     {
      println("Error") ;
      submit(Sys.FinalMarker, Custom_3_out0) ;
     }

     rstring token = getJSONField(S.data, "access_token") ;
     submit({ token = token }, Custom_3_out0) ;
    }

  }
}
To extract authentication token from JSON response, a simple Java function is used
/* Generated by Streams Studio: November 6, 2015 12:16:07 AM GMT+01:00 */
package application;


import java.io.IOException;

import com.ibm.json.java.JSONObject;
import com.ibm.streams.function.model.Function;

/**
 * Class for implementing SPL Java native function. 
 */
public class GetJSONFieldImpl  {

    @Function(namespace="application", name="getJSONField", description="", stateful=false)
    public static String getJSONField(String JSON,String field) throws IOException {
     JSONObject obj = JSONObject.parse(JSON);
     return (String) obj.get(field);
    }
    
}
Using HTTPost operator and wrapper Java function to extract JSON data we can implement all POST Power BI REST API calls.
So far so good, but what about GET method to receive, for instance, a list of all data sets in Power BI dashboard or DELETE method to remove all rows from Power BI table?
Unfortunately, I was unable to make GET REST API call through  HTTPGetStream operator. This is because the HTTPGetStream operator is designed to provide a constant flow of input data, it is not designed for a single request-response action. Although it is possible to make use of this operator, I found it very artificial and decided to go the other way. Needless to say, HTTP DELETE method is not available at all.
Next approach, Java callouts
Prior to InfoSphere Streams application, I implemented a small Java utility class for accessing Power BI REST API calls. To avoid any complications, Java class is designed as a set of static stateless methods, without class instances or local variables. It is the client responsibility to store and pass Power BI authentication token. This way Java methods can be executed in parallel, which is essential for Streams development.
Source code for Java class is available here. The only dependency is GSON library and Apache HttpComponents. Usage examples are available here.
Power BI table schema definition
Table schema is defined as a Map<String, String> table schema. Map key is column name and value is column data type. Data types available are enumerated in PowerBI.java.

/** PowerBI column types */
 public static final String INT64_TYPE = "Int64";
 public static final String STRING_TYPE = "string";
 public static final String DOUBLE_TYPE = "Double";
 public static final String BOOL_TYPE = "bool";
 public static final String DATETIME_TYPE = "DateTime";
Using Java map is very convenient, the only disadvantage we cannot control the order of columns. It is ordered by column names alphabetically.
List of rows to be uploaded to Power BI table
A single row cell is defined by TableValue class. The table constructor determines the cell types. Example for integer value:

public TableValue(long intvalue) {
   this.intvalue = intvalue;
   vType = valueType.isint;
   stringvalue = null;
   doublevalue = 0;
   boolvalue = false;
   timeValue = null;
   timeS = null;
  }
Null value is supported as well. One row is specified by Map<String, TableValue>. The list of rows, chunk of data to be loaded, is defined by list of rows List<Map<String, TableValue>> 
List of methods
  • getAuthToken Authentication and procuring the authentication token
  • getDataSets Get list of datasets
  • getDataSetId Get data set id of a particular dataset
  • getDataSetTables Get list of tables of a particular dataset
  • createDataSet Create data set if does not exist and create a table belonging to this dataset
  • addTableRows Push data into the table
  • clearTable Remove content from the table
  • updateTableSchema Update schema of existing table. Important: this REST API call seems not working
  • checkTableDataSet Check if dataset and table exist, creates if necessary
Remarks
  • Only one table per dataset is created.
  • Columns in table schema is ordered alphabetically
  • Update table schema is not working
Next steps
Create Streams operator for pushing data into Power BI table.

niedziela, 18 października 2015

Byliśmy na koncerce

21 października byliśmy na koncercie muzyki organowej w kościele pod wezwaniem św. Antoniego Padewskiego , podobało nam się bardzo. Wykonawcą był włoski wirtuoz Gabrielle Catalucci. Na program składała się muzyka barokowa głównie kompozytorów włoskich, od wczesnego baroku, który reprezentował  Girolamo Frescobaldi aż do J.S. Bacha. Alessandro Scarlatii  głównie zasłynął jako autor oper i muzyki kameralnej, ale zostawił po sobie także kilka utworów na organy. Jego syn, równie sławny Domenico Scarlati, oprócz kilkuset sonat na instrumenty klawiszone, dzisiaj wykonywanych na klawesynie lub fortepianie, napisał także cztery sonaty na organy, z których jedna została wykonana na koncercie.  Antonio Vivaldi nie tworzył muzyki na organy solo, ale trafił na koncert za pośrednictwem niemieckiego kompozytora Johanna Gottfrieda Walthera. J.G. Walther zasłynął dzięki transkrypcjom na organy utworów innych kompozytorów, na koncercie w kościele św Antoniego została wykonana transkrypcja koncertu skrzypcowego Vivaldiego E minor, op 4, No 2 RV 279. Bardzo dynamiczny i efektowny utwór nic nie stracił w wersji organowej. Walther był współczesny J.S. Bachowi i to prawdopodobnie za jego przykładem Bach napisał własne transkrypcje kompozycji Vivaldiego na organy i klawesyn, a także tranksypcje utworów innych kompozytorów, wzbogacając w ten sposób swój warsztat muzyczny. Pomimo, że nie Bach był tutaj głównym autorem, to te transkrypcje są bardzo chętnie wykonywane także dzisiaj.
Koncert, podobnie jak całą epokę baroku w muzyce, dopięło kilka kompozycji J.S. Bacha.

czwartek, 8 października 2015

New features in Jython MVP framework

Introduction
I added several improvements to Jython MVP framework. The source code is available here. Demo version running in  Google App Engine is here.
Change focus
This feature allows an automatic jump to the particular field in the form. Is useful at the beginning allowing user start entering data in the proper field or, in case of error detected, move to the wrong field. This option is demonstrated in the demo application, Alerts -> Dialog with focus.

More detailed description.
Field with dynamic list of images
This option was created firstly for the editable list. But now it is extended to form as well. Declaration and handling are very similar. More details. The option is showed in the demo application, Alerts -> Dialog with image field.

sobota, 26 września 2015

MSSQL -> DB2 migration, useful awk scripts

Introduction
Database migration is a painstaking task, mostly manual, but with a little help of simple awk scripts it can be alleviated. As an example, I'm using Adventure Works Databases 2012 sample data and by means of "Generate scripts" tool I'm creating text scripts containing all SQL objects.
First task
Output scripts are encoded as UTF-16, therefore one can spend a lot of time running grep command without any result. Thus, the first thing to do is to transform scripts to UTF-8 encoding and change CRLF line break to LF.
iconv -f UTF-16 $1 | dos2unix >`basename $1`
List of objects
Before starting any activity, it is a good idea listing all objects to migrate and prepare a working spreadsheet to track migration progress (listobjects.awk)
awk -v objtype=ALL -f listobjects.awk iscript.sql  >list.csv


Tables
For tables migration very useful is free tool DCW (Data Conversion Workbench). This tool extracts table schema directly from MSSQL and transforms them to DB2 format. Important: the tool does not handle foreigns key correctly, but it can be improved by a Python tool.
Split objects into files
Although MSSQL "Generate scripts" tool allows exporting objects into separate files, very often we have the objects in one big text file.  Nevertheless, during migration it is very convenient to have a single object in a single file and migrate them one after one. obj.awk script executes this task.
awk -v extracttype=ALLOTHERS -f obj.awk iscript.sql 
As a result, all objects except tables, are put into directories: views, triggers, types, procedures and function.
Example: view directory
sbartkowski@linuxmachine:~/work/testb$ ls views
humanresources_vemployeedepartmenthistory.sql  production_vproductanddescription.sql           sales_vindividualcustomer.sql
humanresources_vemployeedepartment.sql         production_vproductcatalogimages.sql            sales_vpersondemographics.sql
humanresources_vemployee.sql                   production_vproductcatalog.sql                  sales_vsalespersonsalesbyfiscalyears.sql
humanresources_vjobcandidateeducation.sql      production_vproductmodelcatalogdescription.sql  sales_vsalesperson.sql
humanresources_vjobcandidateemployment.sql     production_vproductmodelinstructions.sql        sales_vstorewithaddresses.sql
humanresources_vjobcandidate.sql               production_vworkorderrouting.sql                sales_vstorewithcontacts.sql
person_vadditionalcontactinfo.sql              purchasing_vvendorwithaddresses.sql             sales_vstorewithdemographics.sql
person_vstateprovincecountryregion.sql         purchasing_vvendorwithcontacts.sql              sales_vterritorysalesdrilldown.sql
production_vmanufacturinginstructions.sql      sales_vcompanysales.sql

Tables are split using another parameter. As an input file, the result of DCW tool is used.
awk -v extracttype=TABLES -f obj.awk tables.sql
Objects are put into tables and constraint directories.
sbartkowski@linuxmachine:~/work/testb$ ls tables
humanresources_department.sql                 production_location.sql                               purchasing_purchaseorderheader.sql
humanresources_employeedepartmenthistory.sql  production_productcategory.sql                        purchasing_shipmethod.sql
humanresources_employeepayhistory.sql         production_productcosthistory.sql                     purchasing_vendor.sql
humanresources_employee.sql                   production_productdescription.sql                     sales_countryregioncurrency.sql
humanresources_jobcandidate.sql               production_productdocument.sql                        sales_creditcard.sql
humanresources_shift.sql                      production_productinventory.sql                       sales_currencyrate.sql
person_address.sql                            production_productlistpricehistory.sql                sales_currency.sql
person_addresstype.sql                        production_productmodelillustration.sql               sales_customer.sql
person_businessentityaddress.sql              production_productmodelproductdescriptionculture.sql  sales_personcreditcard.sql
person_businessentitycontact.sql              production_productmodel.sql                           sales_salesorderdetail.sql
person_businessentity.sql                     production_productphoto.sql                           sales_salesorderheadersalesreason.sql
person_contacttype.sql                        production_productproductphoto.sql                    sales_salesorderheader.sql
person_countryregion.sql                      production_productreview.sql                          sales_salespersonquotahistory.sql
person_emailaddress.sql                       production_product.sql                                sales_salesperson.sql
person_password.sql                           production_productsubcategory.sql                     sales_salesreason.sql
person_personphone.sql                        production_scrapreason.sql                            sales_salestaxrate.sql
person_person.sql                             production_transactionhistoryarchive.sql              sales_salesterritoryhistory.sql
person_phonenumbertype.sql                    production_transactionhistory.sql                     sales_salesterritory.sql
person_stateprovince.sql                      production_unitmeasure.sql                            sales_shoppingcartitem.sql
production_billofmaterials.sql                production_workorderrouting.sql                       sales_specialofferproduct.sql
production_culture.sql                        production_workorder.sql                              sales_specialoffer.sql
production_document.sql                       purchasing_productvendor.sql                          sales_store.sql
production_illustration.sql
Tranformation
The obj.awk script also performs simple MSSQL->DB2 transformation (modifmsobj.awk)

BEGIN { PRINT = 1 }

function outputline(line) {
  if (PRINT == 1) print line
}

/^@/ { 
     PRINT = 0
     print $0
}

{
  line = gensub(/\[|\]/,"","g",$0)   
  line = gensub(/@/,"v","g",line)   
  line = gensub(/+/,"||","g",line)   
  line = gensub(/ISNULL/,"COALESCE","g",line)   

  outputline(line)
}
  • Removes []
  • Replaces @ variable marker with v character
  • MSSQL uses + for string concatenation. The script replaces + with ||. Important: all + are replaced, also + used for artihtmetic computation.
  • Replaces ISNULL with COALESCE
Also for types a simple tranformation is conducted (modiftypes.awk).
Conclusion
Although MSSQL->DB2 migration is still manual and mundane process by applying very simple automation we can speed it up and make it more organized. Very interesting object splitting combine with transformation. This transformation (take a look into the body of obj.awk script) can be enhanced regarding the needs.

niedziela, 30 sierpnia 2015

AQL, BigInsight Text Analytics and Java, Map/Reduce

Introduction
BigInsight Text Analytics is IBM Hadoop add-on, a tool for extracting information from text data by applying a set of rules. The core of BigInsight Text Analytics is AQL, Annotation Query Language. AQL (similar to SQL) allows to develop text analysis bottom-up, starting with basic elements, like dictionaries and tokens and then build up more complicated statements. The final program is called "extractor" which runs over set of input documents and produces a collection of "views" containing desirable information in a structured form. "View" is like a table (relation) in SQL, sequence of rows composed of a list of columns.
Extractor is AQL program consisting of one or more AQL modules. Every module contains one or more source file with AQL statements.
BigInsight 3 provides Eclipse component to developing, testing and publishing AQL extractor. This component is discontinued in BigInsight 4 and replaced by Web Tool. Unfortunately, there is a design gap in BigInsight 4. The capabilities of Web Tool are very limited and if the developer wants to unleash a full power of Text Analytics AQL beyond Web Tool there is no simple way to do so. So I decided to fill this gap.
Java API
Text Analytics AQL engine can be easily leveraged using Java API and thus integrated with any other Big Data solution. So I created a simple project allowing compiling and executing AQL program.
Prerequisites
Install BigInsight 4 Quick Start Edition. Then find systemT.jar (by executing a command: locate systemT.jar).
Test data, analysis of IBM quarterly business reports,  can be created by running Text Analytics tutorial.
Solution description
The solution can be executed in two different ways: as a standalone program and as a MapReduce task over the Hadoop cluster.
It is recommended, depending on the needs, to prepare two runnable jars: RunAQL.jar (main class com.systemt.runaql.RunAQL) and ExtractMapReduce.jar (main class com.systemt.mapreduce.MapReduceMain).
Parameters
Both methods require three parameters: input directory, output directory, configuration file
Example

java -cp RunAQL.jar:RunAQL_lib/*:/opt/ibm/bignsight/hadbi4/conf com.ibm.runaql.RunAQL hdfs://oc5802506750.ibm.com:8020/user/sbartkowski/$INPUTDIR  hdfs://oc5802506750.ibm.com:8020/user/sbartkowski/$OUTDIR /home/sbartkowski/work/bigi/ma/testj.properties
opt/ibm/bignsight/hadbi4/conf points to Hadoop configuration directory.

  • input directory Input directory with text data to be annotated by Text Analytics extractor. For a standalone program, it could be local directory or hdfs directory (starting with hdfs:)
  • output directory Output directory where Text Analytics result will be stored. In case of Map/Re task, a directory should be removed before. For a standalone program, if could be local directory or hdfs directory (starting with hdfs:)
  • configuration file Configuration file containing information about AQL modules to be executed.
Configuration file
Example
out.tam.dir=hdfs://big64:9000/user/sb/tamdir
#input.multi=hdfs://big64:8020/user/sbartkowski/tamdir
in.module.property1=/home/sbartkowski//TestSystemT/resource/test1/aql/metricsIndicator_dictionaries
in.module.property2=/home/sbartkowski/TestSystemT/resource/test1/aql/metricsIndicator_features
in.module.property3=/home/sbartkowski/TestSystemT/resource/test1/aql/metricsIndicator_externalTypes
in.module.property4=/home/sbartkowski/TestSystemT/resource/test1/aql/main
list.modules=metricsIndicator_dictionaries,metricsIndicator_features,main,metricsIndicator_externalTypes
ex.dictionary1=metricsIndicator_dictionaries.abbreviations,/home/sbartkowski/TestSystemT/resource/test1/resources/abbreviation.dict
  • out.tam.dir For standalone program empty. For Map/Re program,  hdfs directory where compiled AQL program (.tam) files are saved and reused by Map/Re tasks.
  • in.module.propertyN Could be more than 1. Directory with AQL source code module. It is always a local directory.
  • list.modules Should correspond to in.module.propertyN value. List of module names separated by a comma. 
  • ex.dictionaryN Pair separated by comma, external dictionary name and external dictionary file. For Map/Reduce program is should be hdfs address.
  • input.multi Y if Multilingual tokenizer should be used. N or ignore otherwise. More details below.
  • input.lang Language of input documents. List of accepted values. Could be omitted if default English is used.
Standard or Multilingual tokenizer
Although Multilingual tokenizer supersedes Standard, the Standard should be used as often as possible. There is a performance penalty attached to Multilingual tokenizer. Multilingual should be used with no-European languages like Hebrew, Chinese or Japanese. Multilingual tokenizer requires additional dependency. More details (point 3).
An example of launching sequence for Multilingual tokenizer
 OUTDIR=outputh
hadoop fs -rm -r -skipTrash $OUTDIR

ADDJAR=ExtractMapReduce_lib/dlt-3.3.0.jar,ExtractMapReduce_lib/uimaj-core-2.3.1.jar,ExtractMapReduce_lib/an_dlt-3.3.0.jar,ExtractMapReduce_lib/systemT-3.3.0.jar
ADDJAR=$ADDJAR,ExtractMapReduce_lib/icu4j-55.1.jar,ExtractMapReduce_lib/tokenizer-3.3.0.jar
hadoop jar ExtractMapReduce.jar -libjars $ADDJAR Macabbi $OUTDIR testh.properties
testh.properties
out.tam.dir=hdfs://nc9128109010.kraklab.pl.ibm.com:9000/user/sb/hedir 
input.multi=Y 
module.property1=Medical,Util 
list.modules1=aql/Medical 
list.modules2=aql/Util 
input.lang=he
An example of launching sequence for Text Analytics tutorial
OUTDIR=output2
hadoop fs -rm -r -skipTrash $OUTDIR
hadoop jar ExtractMapReduce.jar -libjars ExtractMapReduce_lib/systemT-3.3.0.jar inputdata $OUTDIR test.properties
test.properties
out.tam.dir=hdfs://nc9128109010.kraklab.pl.ibm.com:9000/user/sb/tamdir
in.module.property1=test1/aql/metricsIndicator_dictionaries
in.module.property2=test1/aql/metricsIndicator_features
in.module.property3=test1/aql/metricsIndicator_externalTypes
in.module.property4=test1/aql/main
list.modules=metricsIndicator_dictionaries,metricsIndicator_features,main,metricsIndicator_externalTypes
ex.dictionary1=metricsIndicator_dictionaries.abbreviations,hdfs://nc9128109010.kraklab.pl.ibm.com:9000/user/sb/extdic/abbreviation.dict
Running Text Analytics as Map/Reduce
Annotating text data with Text Analytics AQL is perfectly suited for Map/Reduce paradigm. Input data is divided into a collection of text files and Map task annotates one document.  In most cases, Text Analytics runs over a good number relatively small input documents (for instance: Twitter tweets, medical reports etc). The output is a pair: view name (key) and view content (value). The Reduce task consolidates content of a view from all documents. Next, OutputFormat creates the result (just now only CSV format is supported).



Future extensions, known limitations
  • The current version does not support external tables, only external dictionaries are implemented. 
  • Only CSV format is supported. As a feature first line containing the header is desirable. Additional output format: TSV (tab separated values), HBase, Hive or BigSQL (IBM BigInsight add-on) should be added. The main challenge to overcome: how to pass output view column description to the OutputFormat task.
  • It is not necessary to compile AQL source files every time. Compiled .tam file can be created once, stored and reused later, in a separate process.

środa, 19 sierpnia 2015

Pro*C to DB2 Embedded SQL migration, structures

Introduction
Embedded SQL (DB2) and Pro*C (Oracle) allow to use C structure as hosts variable. Instead of enumerating all columns in INSERT or SELECT statement the developer can use name of the structure variable. Implementation of structure in DB2 and Oracle is similar but a little different.
Oracle code
Pro*C Oracle sample code

create table personal_data (id integer, name varchar(100));
int addPersonalData(personal_data data) {

  int failed;
  
  EXEC SQL WHENEVER SQLERROR DO sql_error("Insert personal_data",&failed);  
 
  EXEC SQL INSERT INTO personal_data (id,name) 
      VALUES (:data); 
  if (failed) return 0;
  
  EXEC SQL INSERT INTO personal_data (id,name) 
      VALUES (:data.id,NULL); 
      
  if (failed) return 0;
  return 1;       
}  

int calculatePersonalStat(int *all, int *nullno) {

   int failed;
   personal_data data;
   struct
        {
   short ind_id;
   short ind_name;
    } data_info_ind;
  
   EXEC SQL WHENEVER SQLERROR DO sql_error("calculate personal stat",&failed);  

   EXEC SQL DECLARE persdata CURSOR FOR 
          SELECT ID,NAME 
              FROM personal_data;
       
   if (failed) return 0;       
       
   EXEC SQL OPEN persdata;       
   
   if (failed) return 0;
   
   EXEC SQL WHENEVER NOT FOUND DO break;   
   
   *all = *nullno = 0;
   
    for (;;) 
    { 

        EXEC SQL FETCH  persdata INTO :data INDICATOR :data_info_ind;        
        if (failed) return 0;
 (*all)++;
 if (data_info_ind.ind_name != 0) (*nullno)++; 
    } 
    
    EXEC SQL CLOSE persdata;
   
    return 1;
            
}     
In this code we see usage of structure to FETCH and INSERT data into the table and also structure as INDICATOR host variable.
DB2 code
Embedded SQL sample code, equivalent of Oracle code above

EXEC SQL BEGIN DECLARE SECTION;

typedef struct { 
  int id;
  char name[100];
} personal_data_struct_db2 ; 

EXEC SQL END DECLARE SECTION;

int addPersonalData(personal_data data) {

    struct sqlca sqlca;
  
   EXEC SQL BEGIN DECLARE SECTION;
      personal_data_struct_db2 rec;
   EXEC SQL END DECLARE SECTION;
     int error = 0;
     
   memcpy(&rec,&data, sizeof(rec));
 
  EXEC SQL INSERT INTO personal_data (id,name) 
      VALUES (:rec); 

      
  EVALRESULT("Insert personal rec");
  if (error) return 0;
  
  EXEC SQL INSERT INTO personal_data (id,name) 
      VALUES (10,NULL); 

  EVALRESULT("Insert personal rec with null");
  if (error) return 0;
      
  return 1;       
}  

int calculatePersonalStat(int *all, int *nullno) {

   struct sqlca sqlca;
  
   EXEC SQL BEGIN DECLARE SECTION;
      personal_data_struct_db2 data;
      short data_info_ind[2];
   EXEC SQL END DECLARE SECTION;
     int error = 0;
    
  
//   EXEC SQL WHENEVER SQLERROR DO sql_error("calculate personal stat",&failed);  

   EXEC SQL DECLARE persdata CURSOR FOR 
          SELECT ID,NAME 
              FROM personal_data;
   EVALRESULT("Declare cursor");
   if (error) return 0;
       
   EXEC SQL OPEN persdata;       
   EVALRESULT("Open cursor");
   if (error) return 0;
  
   
   
   *all = *nullno = 0;
   
    for (;;) 
    { 
        EXEC SQL FETCH  persdata INTO :data INDICATOR :data_info_ind;        
        EVALRESULT("Fetch next");
        if (error) return 0;
 if (SQLCODE != 0) break;
 (*all)++;
 if (data_info_ind[1] != 0) (*nullno)++; 
    } 
    
    EXEC SQL CLOSE persdata;
   
    return 1;
            
}         
The DB2 code is similar but there are differences:
  • Structure declaration must be enclosed in EXEC SQL BEGIN DECLARE and EXEC SQL END DECLARE SECTION. Unfortunately it requires duplicating of the structure definiton.
  • Also structure variable should be declared in similar way. Pay attention too memcpy invocation.
  • Also implementation of INDICATOR host variable is different. Istead of structure a table should be used
Conclusion
Despite these differences migration is simple and straightforward. The code should be modifed but no changes in application logic are necessary.