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.

niedziela, 9 sierpnia 2015

IBM InfoSphere Streams and power of parallelism

Introduction
Basic method to improve performance in IBM InfoSphere Streams is to apply parallelism. Unlike any other programming framework the parallelism in Streams can be achieved using very simple method and the result is amazing.
Application
To prove it I created a very simple application calculating FFT (Fast Fourier Transformation) on a series of numbers. Choice of FFT is arbitrary, it is only an example of CPU thirsty method without paying any attention to validity of input and output values.
Source code of the application is available here. It produces series of random numbers (SeriesSource.spl), aggregate them in 512 size buffer and apply FFT (FFTPerform.spl) and consume  the result (SinkFFT.spl). The performance is gauged by measuring number of FFT calculations output flowing to SinkFFT.
All tests were conducted on two VMWare machines (4 virtual CPUs and 4 GB memory).
First step
First version does not contain any parallel calculation.

The performance was 23-25 tuples/sec (number of FFT output failing into SinkFFT).
Second step
It is very easy to detect that bottleneck is FFT operator. The simplest way to execute FFT in parallel is apply UDP (User Define Parallelism) to FFT.

  
                    @parallel(width = 5)
                    (stream<list<float64> l> Out0) as FFT_2 =
    FFT(Custom_1_out0)
   {
    param
     algorithm : DCT ;
     inputTimeSeries : l ;
     resolution : 1024u ;
    output
     Out0 : l = magnitude() ;
   }

So we have 5 FFT operators running in parallel and performance is 86-86 tuples/ per second.
Third step
Next step is to distribute calculation between two nodes. So we have to create two instances of FFTPerform operator and put them into separate PE (Processing Element).
namespace application ;

use com.testperf.series::SeriesSource ;
use com.testperf.fft::FFTPerform ;
use com.testperf.sink::SinkFFT ;

composite Main
{
 graph
  (stream<float64 num> SeriesSource_1_out0) as SeriesSource_1 = SeriesSource()
  {
  }

  (stream<list<float64> l> FFTPerform_3_out0) as FFTPerform_3 =
   FFTPerform(SeriesSource_1_out0)
  {
   config
    placement : partitionColocation("CC") ;
  }

  () as SinkFFT_5 = SinkFFT(FFTPerform_3_out0, FFTPerform_6_out0)
  {
  }

  (stream<list<float64> l> FFTPerform_6_out0) as FFTPerform_6 =
   FFTPerform(SeriesSource_1_out0)
  {
   config
    placement : partitionColocation("BB") ;
  }

 config
  placement : partitionColocation("AB") ;
}

We do not have do anything with operator logic, just make a copy of SinkFFT operator in main composite and by using partitionCollocation option put them to separate PE. When application is deployed IBM InfoSphere Streams Scheduler will push them into separate hosts.

Now we have 10 FFT operators running in parallel and distributed evenly between two hosts. The performance is 120-125 tuples per second.
Conclusion

  • The default model for Streams application is maximum parallelism, every operator in a separate PE (process). But is does not make any sense, having random tens or hundreds PE (processes) does not improve performance. So a good starting point is fuse all application into single PE (no parallelism at all) and later introduce parallelism according to design.
  • Firstly we have to decide how to measure performance. In the test application above it was very simple but in case of complex application it requires some effort.
  • Next step is identify bottleneck. In the test application it was obvious but in real application it could require a lot of tests and search.
  • Applying UDP annotation is the a very good method to introduce parallelism. But selecting the optimal parallel level number requires some effort, greater number does not mean better performance. The rule of thumb is that should not exceed the number of cores in the host machine.
  • Parallelism is not limited only to multiplying number of threads conducting a particular task but also spraying execution between different hosts. Identifying which operator or job should be distributed into separate host to achieve maximum performance requires some effort. 

niedziela, 19 lipca 2015

Byliśmy na koncertach

28 czerwca byliśmy na koncercie muzyki organowej w kościele Ewangelicko-Reformowanym przy ul Solidarności, podobało nam się bardzo. Repertuar składał się z utworów kompozytorów XIX i XX wiecznych, zaś utwory dawniejszych mistrzów we współczesnym opracowaniu na organy. Bardzo dobrze brzmiało "Priere" Cesara Francka. Ukłonem w stronę publiczności było organowe wykonanie znanego Poloneza op 40 no 1 Chopina w transkrypcji organowej, gdyby ktoś usłyszał ten utwór pierwszy raz mógłby pomyśleć, że to właśnie organy było instrumentem na który to dzieło zostało pierwotnie napisane. Końcowym akcentem były organowe wariacje na temat znanej "Sarabandy/La Folia" Haendla (HWV 437). Jest ciekawostką, że ta sama "Sarabanda" w wersji orkiestrowej posłużyła jako ilustracja muzyczna w znanym filmie akcji Raid 2, i to jeszcze w jednej z najbardziej ociekających krwią scen (nie klikaj, jeśli nie lubisz takich filmów). Trudno sobie wyobrazić większy kontrast z pięknym i surowym wnętrzem kalwińskiego kościoła, gdzie koncert miał miejsce. Zapewne i samemu wielkiemu Haendlowi nie śniło się, że ten skromny fragment suity na klawesyn po latach znajdzie takie różnorodne zastosowanie.

5 czerwca byliśmy na koncercie orkiestry Sinfonia Viva, podobało nam się także. Tutaj prezentowany był repertuar kompozytorów XX wiecznych.  Oba koncerty miały jeden łączący element, znaną Etiudę d-moll op. 2 nr 3 Karola Szymanowskiego, jedno wykonanie w wersji organowej, a drugie w wersji na orkiestrę smyczkową. Była to jedna z najwcześniejszych kompozycji Szymanowskiego, która bardzo szybko weszła do światowego repertuaru. Tylko ten sukces miał także drugie dno, gdyż trochę przesłonił późniejszą twórczość kompozytora. Jak sam Szymanowski z goryczą zauważył : "Fatalnie jest już w tak młodym wieku skomponować swoją IX Symfonię!”. Ale główną atrakcją było brawurowe wykonanie znanego cyklu pieśni "Iluminacje" Beniamina Brittena w pierwotnym, sopranowym wykonaniu. Koncert dopełniła mniej znana kompozycja Richarda Straussa "Metamorfozy".


sobota, 11 lipca 2015

New version of MVP Jython framework

Introduction
New version has been deployed. Introduces new functionality and enhancements.
Source code and and Google App Engine demo version.
Hide and show columns in list programmatically
Columns can be hidden and revealed again from Jython code. More detailed description is available here.
Demo version: Different lists->Change header name. After clicking "Column 2 visible" check box "Number 2 column" disappears or reappears.

Remember feature
Entering data is very dull task but "remember feature" provides some relief. Just restores value used last time. So far it could be achieved programmatically but from now on it is done automatically by adding "remember" attribute to a field description. Value can be remembered for each field individually or several fields can share the same value. More detailed description is available here.
Demo version : Alerts -> Dialog with remember.

Fields "Remember us" shares the same remembered value.
Suggest feature
"Remember" recalls only one last value but "Suggest" brings back a list of values to choose from. To implement "Suggest feature" GWT SuggestBox widget is used. Suggest list can be static or dynamic. Static gives the same list of predefined values all the time. Dynamic is enhanced automatically, every time user enters new value it is added to the list. In case of overflow the oldest value is removed. List can be stored individually for each field or the same list can be shared between different fields. More detailed description is available here.
Demo version :  Alerts->Dialog with suggest.

"Suggest custom" is using a static suggest list and "Suggest" dynamic
Get data
Data from any list can be downloaded or send to the recipient. Just click: "Tool" -> "Get data".


Several formats are supported: cvs, xml, html, JSon and PDF.

piątek, 22 maja 2015

Pro*C to DB2 Embedded SQL migration, BLOB, large object

Pro*C BLOB
Uploading and downloading files from and to BLOB column is a painstaking process in Oracle Pro*C. More details: download, read BLOB and upload, write BLOB.
Pro*C code example:
create table blobtable (id number(12), filename varchar2(100), fileb blob);
Code sample for reading and writing BLOB column is here.
One has to recognize whether file can be swallowed in a single mouthful (uff !) and issue EXEC SQL LOB WRITE ONE. If not then a complicated logic should be developed to recognize the first chunk (EXEC SQL LOB WRITE FIRST), intermediate chunk(s) (EXEC SQL LOB WRITE NEXT) and the closing chunk (EXEC SQL LOB WRITE LAST). The same for reading a BLOB column. DBMB_LOB utility is only a partial solution because it can read/write files from and to Oracle server.
Embedded SQL (DB2) BLOB
In DB2 (Embedded SQL) it is much simpler. Just use BLOB_FILE host variable for BLOB reading and writing. The whole code looks very simple now. Works also while executed at the client or server side.
int writeBlob(char *filename) { 
   struct sqlca sqlca;
   int error = 0;
  EXEC SQL BEGIN DECLARE SECTION;
    int id;
    char pfilename[500];
    SQL TYPE IS BLOB_FILE FILEB;
  EXEC SQL END DECLARE SECTION;
      
   strcpy(pfilename,filename);

   EXEC SQL select id into :id FROM blobtable WHERE id = 1  ;
   EVALRESULT("Select blob record");
   if (error) return 0;
   
   if (sqlca.sqlcode == 100) {
     // not found
     EXEC SQL INSERT INTO blobtable VALUES(1,:pfilename,NULL);
     EVALRESULT("Insert record to blobtable");
     if (error) return 0;
   }     
   strcpy (FILEB.name, pfilename);
   FILEB.name_length = strlen(pfilename);
   FILEB.file_options = SQL_FILE_READ;
   EXEC SQL UPDATE blobtable SET filename=:pfilename,fileb=:FILEB WHERE ID=1;
   EVALRESULT("Insert blob value");
   if (error) return 0;
   
   return 1;
 
 
}

int readBlob(char *filename) { 
   struct sqlca sqlca;
   int error = 0;
  EXEC SQL BEGIN DECLARE SECTION;
    SQL TYPE IS BLOB_FILE FILEB;
  EXEC SQL END DECLARE SECTION;
      
   
   strcpy (FILEB.name, filename);
   FILEB.name_length = strlen(filename);
   FILEB.file_options = SQL_FILE_OVERWRITE;

   EXEC SQL select fileb into :FILEB FROM blobtable WHERE id = 1  ;
   EVALRESULT("Read blob record");
   if (error) return 0;
   
   printf("sql code=%u\n",sqlca.sqlcode);
   if (sqlca.sqlcode == 100) {
     // not found
     printf("No record in blobtable table\n");
     return 0;
   }     
   
   return 1;
 
  
}

niedziela, 17 maja 2015

New version of MVP Jython framework

Introduction
New version of MVP Jython framework was deployed. New features had been added and several bugs fixed.
Sample application (Google App Engine)
Source code
Change title(tooltip)
More detailed description 
Change column header
More detailed description
Sample application Different lists -> Change column header
After clicking "Change column header" button column header will be modified accordingly.
Changes saving
User can modify some parameters related to list. For instance: number of rows.

So far the changes were active only for a current window. After refreshing standard values were restored. From now on the changes will be persisted in browser cookies and reused.
Modify the list layout
Additional option is implemented : "Change column settings"


After selecting the option it is possible to modify column headers and visibility.

Changes are persistent, new layout is stored in browser cookies. It is possible also to restore standard layout by clicking "Restore default" button.
Important: changing column order is planned in the future.
Setting/unsetting all column
Sample application Different lists -> List check
In "list edit" mode (more details) one can make "bool" column editable for check/uncheck. Option has been added to check/uncheck all list after clicking the column header. Whole list is modified, not only a single row.
Important: Side effect is involved because clicking the header causes also list sorting by boolean values. Will be improved in the future.
Small fixes
  • Footer numerical value is formatted like number in the column (decimal point, spaces between thousands). 
  • Jython 2.7 (final release) is used. Unfortunately, nasty problem was detected while converting date time value to Java Timestamp (source code modifier). Also this version does not work in Google App Engine (java.io package) so the old Jython 2.7-b3 is deployed for this version.
Future
  • List: download as csv, PDF (for printing) and send as an attachment in mail.

piątek, 15 maja 2015

New version of JSPWiki In The Cloud

I deployed new version of Google App Engine JSPWiki port. Caching mechanism was added (by means of Google App Engine MemCache). Performance improves a little bit but it is not a significant change.
Sources are available on GitHub.
I also moved demo version to http://myjspwiki.appspot.com/ because previous location is infested by spam.

niedziela, 12 kwietnia 2015

Pro*C to DB2 Embedded SQL migration, vars, structures, SP and UDF

Vars and structs

Pro*C Oracle code

#define SIZEKEY 20

int CalculateSum(float *sum,int keyid,struct retinfo *ret)
{

  char key[SIZEKEY+1];
/* Type equivalence key to the string external datatype.*/
  EXEC SQL VAR key is string(SIZEKEY);
  float sumk;
  int failed = 0;

  sprintf(key,"%u",keyid);

  printf("Calculate sum for %s\n",key);

  EXEC SQL WHENEVER SQLERROR DO sql_error("Calculate sum",&failed);
  
  EXEC SQL select sum(num) into :sumk FROM numhistory WHERE numkey = :key  ;

  if (failed) return 0; 
  
  ret->keyid = keyid;
  EXEC SQL select count(num) into :ret->no FROM numhistory WHERE numkey = :key  ;

  if (failed) return 0; 
  
  *sum = sumk;
  return 1;
}
Two problems require attention here while migrating to DB2.
First

EXEC SQL VAR key is string(SIZEKEY);
DB2 Embedded SQL does not support it. All hosts variables should be enclosed in :
EXEC SQL BEGIN DECLARE SECTION;
 .....
EXEC SQL END DECLARE SECTION;
Another
 EXEC SQL select count(num) into :ret->no FROM numhistory WHERE numkey = :key  ;
DB2 does not support struct member as a host variable. So value should be firstly assigned to simple variable and secondly to struct member. So finally DB2 Embedded SQL code equivalent to Pro*C code

int CalculateSum(float *sum,int keyid,struct retinfo *ret) {
  
  struct sqlca sqlca;
  
EXEC SQL BEGIN DECLARE SECTION;
    char key[21];
    float sumk;
    int no;
EXEC SQL END DECLARE SECTION;
  int error = 0;

  sprintf(key,"%u",keyid);

  printf("Calculate sum for %s\n",key);
  
  EXEC SQL select sum(num) into :sumk FROM numhistory WHERE numkey = :key  ;
  EVALRESULT("Calculate sum");
  if (error) return 0;
  ret->keyid = keyid;
  EXEC SQL select count(num) into :no FROM numhistory WHERE numkey = :key  ;
  EVALRESULT("Calculate num");
  ret->no = no;   
  *sum = sumk;

  return 1;
}
SP calling
Pro*C Oracle code
int CallProc(int *res,char* sou) {
  
  int failed = 0;  
  
  printf("Call SP for %s\n",sou);

  EXEC SQL WHENEVER SQLERROR DO sql_error("Call SP",&failed);
  
  EXEC SQL EXECUTE
  BEGIN
    SIMPLEPROC( :res, to_number( :sou ) );
  END;
  END-EXEC;

  if (failed) return 0; 
  
  return 1;
}
DB2 supports notation EXEC SQL EXECUTE ... END-EXEC after setting "Oracle Compatibile Mode" in db2 prep invocation (example) :
db2 prep $1.sqc bindfile SQLWARN NO COMPATIBILITY_MODE ORA

Another problem is related to
SIMPLEPROC( :res, to_number( :sou ) );

DB2 does not support UDF invocation as SP parameter. So this launching sequence should be split into two : UDF invocation and consecutive SP calling.
DB2 equivalent to Pro*C code

int CallProc(int *res,char* sou) {
  
  int error = 0;  
  struct sqlca sqlca;
  
  EXEC SQL BEGIN DECLARE SECTION;
    char psou[100];
    int number;
    int resp;
  EXEC SQL END DECLARE SECTION;

  printf("Call SP par=%s\n",sou);
  
  strcpy(psou,sou);
  
  EXEC SQL VALUES (TO_NUMBER(:psou)) INTO :number;
  EVALRESULT("Call TO_NUMBER");
  if (error) return 0;

  
  EXEC SQL EXECUTE
  BEGIN
    SIMPLEPROC( :resp, :number );
  END;
  END-EXEC;
  
  EVALRESULT("Call SP");

  if (error) return 0; 
  
  *res = resp;
  
  return 1;

}

UDF calling
Pro*C Oracle code
int CallFunc(int *res, int par) {
  int failed = 0;  
  
  printf("Call Func for %u\n",par);

  EXEC SQL WHENEVER SQLERROR DO sql_error("Call UDF",&failed);
  
  EXEC SQL EXECUTE
  BEGIN
    :res := RETPROC( :par );
  END;
  END-EXEC;
  
  if (failed) return 0; 
  
  return 1;

}
DB2 does not support this launching method even in Compatibility Mode. DB2 Embedded SQL equivalent
int CallFunc(int *res, int par) {
  int error = 0;  
  struct sqlca sqlca;
  
  EXEC SQL BEGIN DECLARE SECTION;
    int resp;
    int parp;
  EXEC SQL END DECLARE SECTION;
  
  printf("Call UDF par=%u\n",par);
  
  parp = par;  
  
  EXEC SQL VALUES (RETPROC(:parp)) INTO :resp;
  EVALRESULT("Call UDF");
  if (error) return 0;
  
  *res = resp;
  
  return 1;
}  
Additional remarks

  • Full source code (including makefiles and sql) can be found here.
  • Next : BLOB handling

niedziela, 5 kwietnia 2015

Pro*C to DB2 Embedded SQL migration, connection

Introduction
Embedded SQL is traditional method of database programming but still very robust and effective. All major SQL vendors supports Embedded SQL technology. Unfortunately, although general structure for different Embedded SQL implementation contains a lot of shared elements migration from one vendor to another is not easy. Nevertheless,  "not easy" does not mean impossible, if one recognizes some common pattern then migration is pretty simple and allows keep the main solution logic almost intact.
Pro*C is Embedded SQL implementation for Oracle, just Embedded SQL for DB2. Below I will describe some common problems and solutions found during migrating Oracle C++ Pro*C code to DB2 starting from connecting to and disconnecting from database.
Oracle Compatibility Mode
Starting from version 9.7 DB2 contains Oracle Compatibility Mode (constantly improved and enhanced in subsequent version up to 10.5) making migration of SQL procedure code almost painless. There is also "compatibility mode" for Pro*C. It contains a lot of useful features but most of the problems should be resolved manually.
Sample program
I created a simple program which connects to and disconnects from database the show the first basic problems. The source code is available here.
Compiling, linking, declaration
Implementation for DB2 and Oracle is different. Below are  examples of make files.
Oracle: mk.m
DB2 : mk.m
Additional differences
DB2 requires all host variables (passing data between SQL and C++ code) being declared in a special section. Example:
  EXEC SQL BEGIN DECLARE SECTION;
    char dbAlias[15];
    char user[128 + 1];
    char pswd[15];
  EXEC SQL END DECLARE SECTION;
Also every procedure containing any Embedded SQL code should have a direct struct sql declaration or EXEC SQL INCLUDE SQLCA clause.
void ConnectTo() 
{    
  struct sqlca sqlca;
...
}

Connecting and disconnecting in Oracle
Oracle : Source file
#include <stdio.h>
#include <string.h>
#include <sqlca.h>
#include <stdlib.h>
#include <sqlda.h>
#include <sqlcpr.h>

#include "../utillib.h"

static char * connection = "test/test@think:1521/testdb";

void sql_error(char *msg,int *failed) 
{ 
    char err_msg[512];
    size_t buf_len, msg_len;

    EXEC SQL WHENEVER SQLERROR CONTINUE;

    printf("\n%s\n", msg);

/* Call sqlglm() to get the complete text of the
 * error message.
 */
    buf_len = sizeof (err_msg);
    sqlglm(err_msg, &buf_len, &msg_len);
    printf("%.*s\n", msg_len, err_msg);

    EXEC SQL ROLLBACK RELEASE;
    *failed = 1;
} 


void ConnectTo() 
{    
    int failed = 0;
    printf("Connection string : %s\n",connection);
 
    EXEC SQL WHENEVER SQLERROR DO sql_error("Connection error",&failed);
     
    EXEC SQL CONNECT :connection;
    if (failed) {
       printf("Not connected\n");
       exit(EXIT_FAILURE);
    }       

    printf("Connected to ORACLE\n"); 
}

void Disconnect() 
{
   EXEC SQL WHENEVER SQLERROR CONTINUE;
   EXEC SQL COMMIT WORK RELEASE;
   printf("\nDisconnected");
}
Migration to DB2
The main problem is hidden in :
    EXEC SQL WHENEVER SQLERROR DO sql_error("Connection error",&failed);
     
    EXEC SQL CONNECT :connection;
    if (failed) {
    ...
    }
In case of any error sql_error routine is called and the routine prints error message and set error variable to 1 just allowing implement a logic for connection failure. This routine can be shared between all SQL invocations and provide common error handling.
Although DB2 Embedded SQL supports declaring custom routine for SQL error handling the routine does not accept any parameter just making direct migration of above code impossible. Passing data using global static variable is very bad programming practice and should be omitted.
The best solution is to use a reusable macro providing common method of error handling.
void SqlInfoPrint(char *appMsg, struct sqlca *pSqlca);

#define EVALRESULT(mess) if (sqlca.sqlcode < 0) { SqlInfoPrint(mess,&sqlca); error = 1; }
    
And DB2 version of Pro*C code above :
EXEC SQL CONNECT TO :dbAlias USER :user USING :pswd;
  EVALRESULT("Database -- connect with userid and password");
  if (error) {
    ...
  }
It requires manual changes but the execution flow is exactly the same. The migrated DB2 code (with implementation of SqlInfoPrint procedure) is here.
Next
Oracle EXEC SQL VAR

niedziela, 29 marca 2015

OpenLDAP, Ubuntu, TLS

Problem
I installed OpenLDAP on my Ubuntu 14.04 machine. It worked fine until I tried to authenticate from RHEL 6.6 box. RHEL 6.6 LDAP client requires TLS connection and there is not way to persuade it to change its mind. So I enabled OpenLDAP server for TLS connection using advice provided here. But in turn OpenLDAP server refused to restart. In /var/log/syslog file I found enigmatic entry:

Mar 29 00:47:57 sb-ThinkPad-W540 slapd[11071]: connections_destroy: nothing to destroy.
Mar 29 00:47:57 sb-ThinkPad-W540 kernel: [14766.527083] type=1400 audit(1427586477.983:83): 
apparmor="DENIED" operation="open" profile="/usr/sbin/slapd" name="/usr/share/p11-kit/modules/" 
pid=11070 comm="slapd" requested_mask="r" denied_mask="r" fsuid=122 ouid=0
Solution
After browsing I discovered that there exists linux kernel guardian called AppArmor and this Cerberus denies OpenLDAP server access to some configuration directories. But the solution was quite simple. Adding several lines to slapd profile opens this gate and now my LDAP server works as expected
File /etc/apparmor.d/usr.sbin.slapd
/usr/share/p11-kit/modules/ r,
/usr/share/p11-kit/modules/* r,
/usr/lib/x86_64-linux-gnu/pkcs11/ m,
/usr/lib/x86_64-linux-gnu/pkcs11/* m,

piątek, 13 lutego 2015

SQL Server to DB2 migration, UDF

Introduction
One of the element of any migration is built-in functions conversion. Some of them are the same, some are similar but different and some does not have any direct equivalent and requires additional developing. Freely available http://www.redbooks.ibm.com/redbooks/pdfs/sg246672.pdf ("Function mapping" chapter) contains a good number of examples how to convert SQL Server built-in functions to DB2.
Conversion is quite easy but what to do if we want to get SQL statements migrated to DB2 and keep backward compatibility at the same time.
It is more complicated but also possible. I created a simple project with examples - it is available here.
Solution for DateAdd, DateDiff and DatePart
The main problem is DateAdd and DateDiff functions. Example:
SELECT DATEADD(month, 1, '2006-08-30');
Unfortunately, this statement cannot be migrated directly to DB2 because DB2 does not support enumeration type. So it is necessary to modify also SQL Server statement to achieve backward compatibility.
Instead of using DATEADD(month ...) function replace it with DATEADD_MM function.
CREATE FUNCTION DATEADD_MM(@mins INT, @da DATETIME)
RETURNS DATETIME
AS 
BEGIN
  RETURN DATEADD(mm,@mins,@da)
END 
and modify the statement (all not built-in UDF in SQL Server should be qualified with schema name).
SELECT dbo.DATEADD_MM(1, '2006-08-30');
DB2 equivalent
CREATE OR REPLACE FUNCTION DBO.DATEADD_MM(IN NOD INT, IN DAT TIMESTAMP)
RETURNS TIMESTAMP
RETURN DAT + NOD MONTHS
@
and DB2 SQL stamement
SELECT dbo.DATEADD_MM(1, '2006-08-30') FROM SYSIBM.DUAL;
After this transformation function dbo.DATEADD_MM can be used any way in the SQL statements executable in SQL Server and DB2 without any modification.
In the files:  DB2 and SQL Server are more examples for DATEADD, DATEDIFF and DATEPART function.
Solution for other built-in scalar functions
Other functions are easy to migrate, just create its DB2 equivalent.
Example:
SELECT SUBSTRING('ABCDEF',2,2)
DB2 equivalence
CREATE OR REPLACE FUNCTION SUBSTRING (IN STR VARCHAR(32672),IN STA INT, IN LEN INT)
RETURNS VARCHAR(32672)
  RETURN SUBSTRING(STR,STA,LEN,CODEUNITS16)
@
More examples:  DB2 and SQL Server
Performance issue
A question can be raised if the performance will not suffer. The question is worth considering because instead of replacing SQL Server function by DB2 equivalent we are adding additional wrapper function to keep backward compatibility. Using additional UDF (particularly in WHERE clause ) can impact performance.  But it is not the case because in most case it is "inline" function or even "source" DB2 function which does not involve any additional function calling.
Conclusion
In most cases it is not a problem to migrate SQL Server build-in scalar function to DB2. Adding some consideration also migrating and keeping backward compatibility is possible.

niedziela, 1 lutego 2015

New version of JavaHotel, taxation

Introduction
I uploaded new version of JavaHotel application. Demo version (U/P user/user) is available here, source code here.
Taxation
While issuing a document (receipt, bill) a taxation is calculated using tax level assigned to the service.

Detailed information related to the document is also available. This information if stored in the database (as XML file) for every document issued.
Taxation information is also exposed in "Stay summary" window providing information about the current status of the stay.
Next step 
Receptionist journal registering the information about user activity.

Ubuntu 14.04 and USB tethering

Introduction
For some reason I was unable to connect to a hotel wi-fi network from my Ubuntu 14.04 desktop. What is more interesting, I connected without any problem my Kindle reader and mobile (Galaxy S2, Android 2.3.6). The hotel receptionist was very kind but also threw up his hand.
Solution
After spending some time googling on my mobile I found the solution and it was extremely simple, connect to the Internet through a mobile using USB tethering.

  1. Find a public place with wi-fi connection and install usbip package (yum install usbip)
  2. Load vhci-hcd module (modprobe vhci-hcd)
  3. Be sure that mobile is connected to wifi network
  4. Connect mobile and desktop via USB cable
  5. Enable USB Tethering in your mobile (Application -> Setting -> Wireless and network -> Tethering and portable hotsp -> Enable Tethering
And that's final, I have raised from death and connected to the world again.