Db2

Db2

Connect with Db2, Informix, Netezza, open source, and other data experts to gain value from your data, share insights, and solve problems.

 View Only

How do you like your eggs? Conditional Compilation in DB2 

Tue March 10, 2020 07:37 PM

Posted by: Serge Rielau

Just recently in DB2 9.7.5  we introduced the HEXTORAW function.
This function converts a hex-string into a VARCHAR FOR BIT DATA and is thus the inverse of the HEX function.

But is it really? Not quite. the HEX function dumps the internal representation of all sorts of types, not just strings.
 
So the question arises: How hard would it be to add other conversion functions which re-constitute values from their hex-dump?
Perhaps I'll write a little library and publish it here in the near future, but for now let's look at one such function only: HEXTOINT
 
On my laptop when I run
VALUES HEX(12345)
I get:
1
--------
39300000
This is the hexa-decimal presentation of a binary 4 byte integer in little-endian architectures.

HEXTOINT('39300000')
Should return:  12345
 
Here is what I came up with. Note that for high performance I would likely choose an UNFENCED C-UDF.
But SQL PL is so much more portable.
--#SET TERMINATOR @
CREATE OR REPLACE FUNCTION HEXTOINT(arg CHAR(8)) RETURNS INTEGER
SPECIFIC HEXTOINT
BEGIN
  DECLARE i     INT;
  DECLARE res   DECFLOAT DEFAULT  0;
  DECLARE nibble CHAR(1);
  SET i = 7;
 
  WHILE i >=  0 DO
    SET nibble = SUBSTR(arg, i, 1);
    SET res = res * 16
        + CASE WHEN nibble BETWEEN '0' AND '9' THEN ASCII(nibble) - ASCII('0')
               WHEN nibble BETWEEN 'A' AND 'F' THEN ASCII(nibble) - ASCII('A') + 10
               WHEN nibble BETWEEN 'a' AND 'f' THEN ASCII(nibble) - ASCII('a') + 10
               ELSE RAISE_ERROR('78000', 'Not a hex-string') END;
    SET i = i + 1;
    SET nibble = SUBSTR(arg, i, 1);
    SET res = res * 16
        + CASE WHEN nibble BETWEEN '0' AND '9' THEN ASCII(nibble) - ASCII('0')
               WHEN nibble BETWEEN 'A' AND 'F' THEN ASCII(nibble) - ASCII('A') + 10
               WHEN nibble BETWEEN 'a' AND 'f' THEN ASCII(nibble) - ASCII('a') + 10
               ELSE RAISE_ERROR('78000', 'Not a hex-string') END;
    SET i = i  -3;
  END WHILE;
  -- Exceeding MAXINT? Then flip to negative
  IF res > 2147483647 THEN
    SET res = res - 2147483648 * 2;
  END IF;
  RETURN res;
END
@
--#SET TERMINATOR ;

 VALUES HEXTOINT(HEX(12345));
1
-----------
      12345
Do we handle negative values?
VALUES (HEX(-1), HEXTOINT(HEX(-1)));

1        2
-------- -----------
FFFFFFFF          -1
Done. And thanks to the portable SQL PL  this will work everywhere on DB2 for LUW - or not!
Recall that my laptop is using little-endian.

So now I have to write another SQL PL function doing the same work, but this time in big-endian to support e.g AIX on pSeries.
Then, if I want to publish my code I need to tell customers which DDL to run for which function. This is messy.
 
DB2 itself runs on all these architectures with minimal code-changes.
The way we do this is development is to sue pre-compile options which split the source-code just where it is needed and inject fragments that are endian or OS aware.
So you would see code like:
#define LITTLE_ENDIAN 1
  
#ifdef LITTLE_ENDIAN
 ....
#endif
That way only one sport in the DB2 code needs to decide the endianness and all the other code will compile properly.
 
In DB2 9.7 FP1 we introduced that same concept into SQL PL
 
Instead of #define we can either use global variables or a new register names SQL_CCFLAGS.think of SQL_CCFLAGS as the "-D" option in your C-compiler
and global variables as the #define.
 
SQL_CCFLAGS can be set:
  • For the current session:
    SET CURRENT SQL_CCFLAGS = 'LITTLE_ENDIAN:TRUE'
  • For the entire database
    UPDATE DB CFG USING SQL_CCFLAGS   'LITTLE_ENDIAN:TRUE'
The variables can be of type BOOLEAN, INTEGER or VARCHAR and are types implicitly. In this case LITTLE_ENDIAN is a BOOLEAN and we have set it to TRUE.
We can now add conditional compilation directives into any compiled (not inlined!) SQL PL or PL/SQL code with the exception of anonymous blocks. 
Conditional compilation precedes regular parsing, so you can substitute any fragment of code after the
  • FUNCTION,
  • TRIGGER or
  • PROCEDURE keyword
whether it is a section within a SQL statement or a set of SQL statements.

Global variables are referenced as normal, while SQL_CCFLAGS are referenced with a double under-bar: __LITTLE_ENDIAN  
The condition itself is preceded with a single under-bar: _IF _ELSEIF _THEN _END 
 
For compatibility with Oracle $IF, etc. and $$LITTLE_ENDIAN is also supported in ORA mode.

Equipped with this knowledge we can now write an endian-tolerant HEXTOINT function:
UPDATE DB CFG USING SQL_CCFLAGS "LITTLE_ENDIAN:TRUE";

--#SET TERMINATOR @
CREATE OR REPLACE FUNCTION HEXTOINT(arg CHAR(8)) RETURNS INTEGER
SPECIFIC HEXTOINT
BEGIN
  DECLARE i     INT;
  DECLARE res   DECFLOAT DEFAULT  0;
  DECLARE nibble CHAR(1);
  SET i = _IF __LITTLE_ENDIAN _THEN 7 _ELSE 1 _END ;
 
  WHILE i BETWEEN 0 AND 8 DO
    SET nibble = SUBSTR(arg, i, 1);
    SET res = res * 16
        + CASE WHEN nibble BETWEEN '0' AND '9' THEN ASCII(nibble) - ASCII('0')
               WHEN nibble BETWEEN 'A' AND 'F' THEN ASCII(nibble) - ASCII('A') + 10
               WHEN nibble BETWEEN 'a' AND 'f' THEN ASCII(nibble) - ASCII('a') + 10
               ELSE RAISE_ERROR('78000', 'Not a hex-string') END;
    SET i = i + 1;
    SET nibble = SUBSTR(arg, i, 1);
    SET res = res * 16
        + CASE WHEN nibble BETWEEN '0' AND '9' THEN ASCII(nibble) - ASCII('0')
               WHEN nibble BETWEEN 'A' AND 'F' THEN ASCII(nibble) - ASCII('A') + 10
               WHEN nibble BETWEEN 'a' AND 'f' THEN ASCII(nibble) - ASCII('a') + 10
               ELSE RAISE_ERROR('78000', 'Not a hex-string') END;
    SET i = i  _IF __LITTLE_ENDIAN _THEN -3 _ELSE +1 _END ;
  END WHILE;
  -- Exceeding MAXINT? Then flip to negative
  IF res > 2147483647 THEN
    SET res = res - 2147483648 * 2;
  END IF;
  RETURN res;
END
@
--#SET TERMINATOR ;
A quick test:
VALUES HEX(-35645);

VALUES HEXTOINT(HEX(-35645));
I leave it to you, the reader, to test verify the code works on a power System as well.
All you must do is either:
  • Not set any SQL_CCFLAGS
    In that case the variable is undefined and the _IF will resolve to UNKNOWN
  • Set the SQL_CCFLAGS to 'LITTLE_ENDIAN:FALSE'
You can find more details on SQL_CCFLAGS and conditional compilation in the Information Center
 

#Db2

Statistics
0 Favorited
4 Views
0 Files
0 Shares
0 Downloads

Comments

Sat November 18, 2023 04:49 PM

Your HEXTOINT function in SQL PL for DB2 showcases a clever solution for handling endian differences, using conditional compilation directives and global variables. The introduction of SQL_CCFLAGS allows for adaptability across various architectures. It's a smart approach, ensuring a single codebase that can be configured based on the underlying system. Your code and explanation provide a thoughtful solution for hex-to-integer conversions in a DB2 environment. Well done!