Pages

Wednesday, October 26, 2016

Using Null indicators with program's variables

nullind allows in null in rpg variables and fields

Null is a concept that RPG programmers are going to have to learn. Generations of RPG programmers have used a blank or zero in a variable to denote that it is not used or its value is not given. Null, in the simplest terms, means nothing, and if there is not a value for variable then null would be the ideal value to give it. If I had used a blank instead how am I to know, later, if blank is a valid value or is it being used to denote there is no value?

While null values can be found DDS files and SQL tables, it was not until IBM i 7.3 that I could code variables within my RPG programs that could be null.

In many databases null is just a value that is moved to a variable. But IBM i uses a different approach, null indicators. A variable is null when its null indicator is set on. When DDS files and SQL tables are created the null indicators, one for every field or column, are generated. These indicators remain invisible, hidden within the file. I have found the best place to see them is within the null byte map of a RPG trigger program.

Prior to IBM i 7.3 it was not possible to define variables with a program or procedure that could have a null indicator. This released added a new keyword to the variable definition statement, NULLIND, its presence indicates that the variable has a null indicator, which can then be set to null. In its simplest form the NULLIND can be used like this:

01  ctl-opt alwnull(*usrctl) ;

02  dcl-s Var1 char(3) inz('ABC') nullind ;
03  dcl-s Var2 char(3) nullind ;

04  %nullind(Var1) = *on ;
05  dsply ('Var1 = <' + Var1 + '> ' +
           'NullInd = <' + %nullind(Var1) + '>') ;

06  %nullind(Var2) = *on ;
07  Var2 = 'DEF' ;
08  dsply ('Var2 = <' + Var2 + '> ' +
           'NullInd = <' + %nullind(Var2) + '>') ;

09  dump(a) ;

Line 1: As I want to control how null is used I need to have the appropriate Control Option to user control of null handling.

Line 2: In this variable definition I have initializing it with a value. The NULLIND keyword is present to flag that this variable has an unnamed null indicator.

Line 3: Pretty much the same as the previous definition, but with no initialization value.

Line 4: The %NULLIND built in function is used to change the value of the null indicator for the given variable. I am setting on the null indicator.

Line 5: I am using the Display operation code, DSPLY to display the value within the variable and the value of its null indicator.

Line 6: Setting on the null indicator for Var2.

Line 7: Moving a value to the variable.

Line 8: Using DSPLY to show the value of the variable and its null indicator.

Line 9: Finally I am producing a program dump, I will explain why below.

When the program is executed I see the following output caused by the DSPLY.

DSPLY  Var1 =  NullInd = <1>
DSPLY  Var2 =  NullInd = <1>

While it clearly shows that the null indicators are on, the variables contain the values I gave them.

The program dump, part shown below, does show the null indicators, _QRNU_NULL_VAR1 and _QRNU_NULL_VAR2, and their values.

NAME              ATTRIBUTES   VALUE
_QRNU_NULL_VAR1   CHAR(1)      '1'
_QRNU_NULL_VAR2   CHAR(1)      '1'
VAR1              CHAR(3)      'ABC'
VAR2              CHAR(3)      'DEF'

In my opinion I think IBM got this wrong. If I set on a null indicator I believe its variable should be cleared too.

I can use the NULLIND keyword to define my own null indicator, rather than use the default one. As shown below:

01  ctl-opt alwnull(*usrctl) ;
                                            
02  dcl-s Var1 char(3) nullind(NullVar1) ;
03  dcl-s NullVar1 ind ;

04  NullVar1 = *on ;
05  dsply ('1. NullVar1 = <' + NullVar1 + '>') ;

06  %nullind(Var1) = *off ;
07  dsply ('2. NullVar1 = <' + NullVar1 + '>') ;

08  dump(a) ;

Line 2: This time when I define Var1 I give a name of an indicator in the NULLIND keyword. The indicator given must be unique, i.e. you cannot use the same null indicator for more than one variable.

Line 3: Having given a name of an indicator in the NULLIND I have to define it.

Line 4: Now when I want to move null to the variable's null indicator I use its name, just like using any other indicator.

Line 6: I can still use the %NULLIND BiF with the variable to set the null indicator off.

When the program is executed the Display operation code on lines 5 and 7 show the following:

DSPLY  1. NullVar1 = <1>
DSPLY  2. NullVar1 = <0>

And the program dump shows that there is no _QRNU_NULL_VAR1 variable as the null indicator is NullVar1.

NAME              ATTRIBUTES     VALUE
NULLVAR1          INDICATOR(1)   '0'
VAR1              CHAR(3)        '   '

In data structures if I have a subfield that I want to have a null indicator the indicator has to be included within the same data structure. For example:

01  dcl-ds Ds1 qualified ;
02    SubF1 char(3) nullind(NullSubF1) ;
03    NullSubF1 ind ;
04  end-ds ;

05  %nullind(Ds1.SubF1) = *on ;

06  dsply ('Ds1.NullSubF1 = <' + Ds1.NullSubF1 + '>') ;

It will come as no surprise that when this code is executed I see:

DSPLY  Ds1.NullSubF1 = <1>

I can use data structures to retrieve the full null byte map for a particular file:

01  dcl-f NULLFILE keyed ;

02  dcl-ds F1 likerec(NULLFILER:*input) nullind(F1Nulls) ;
03  dcl-ds F1Nulls likerec(NULLFILER:*input:*null) ;

04  read NULLFILE F1 ;

Line 1: I define the file.

Line 2: I define a data structure that I will be reading the record into. I use the LIKEREC keyword so that the data structure will be like the file's record format, and I am going to be using this data structure for input only. I also have a NULLIND keyword with the name of another data structure.

Line 3: This second data structure is defined using the LIKEREC keyword as the previous one, except it has an additional third parameter: *NULL. This means that this will return the null byte map for the record format into this data structure.

Line 4: The file is read into the first data structure, and the null byte map is automatically moved into the second.

If debug the program after the read I see:

> EVAL f1
  F1.FLD1 = 001.
  F1.FLD2 = ' '
> EVAL f1nulls
  F1NULLS.FLD1 = ' '
  F1NULLS.FLD2 = '1'

FLD2's null indicator is on, therefore, its value is null.

When I first heard of null indicator with array I thought I would initialize all of the unused elements of my array with their null indicator on, then I could use the look up built in function to find the first unused element:

01  dcl-s Array char(1) dim(5) nullind(ArrayNull) ;
02  dcl-s ArrayNull ind dim(%elem(Array)) inz(*on) ;
03  dcl-s i packed(2) ;

04  for i = 1 to 3 ;
05    Array(i) = %char(i) ;
06  endfor ;

07  i = %lookup(*on:ArrayNull) ;                         

08  dsply ('First unused element = <' + &char(i) + '>') ;

Line 1: My array is defined with a NULLIND keyword.

Line 2: Name used in the first arrays NULLIND has to be an array with the same number of elements as the first. I am also using the INZ keyword to initialize all of the array elements as on, i.e. null.

Lines 4 – 6: This is where I load only three of the first array's elements.

Line 7: I am looking for the first unused element in the array, as shouldn't the first unused element be null?

Alas it is not so, when the DSPLY operation is executed I see that the first element's null indicator is still on.

DSPLY  First unused element = <1>

If I take it one stage further and use debug to look at the null indicators I find they are all on:

> EVAL arraynull
  ARRAYNULL(1) = '1'
  ARRAYNULL(2) = '1'
  ARRAYNULL(3) = '1'
  ARRAYNULL(4) = '1'
  ARRAYNULL(5) = '1'

To make this work the way I want I have to set off the null indicator within the For loop:

04  for i = 1 to %elem(Array) ;
05    Array(i) = %char(i) ;
06    ArrayNull(i) = *off ;
07  endfor ;

When I do that then the lookup finds the first unused element:

DSPLY  First unused element = <4>

I need to think long and hard about whether I will be using NULLIND in my programs. In my opinion if the null indicator is set on the value in the variable, data structure sub field, or array element should be initialized. Do you agree? Let me know your opinion by using the Comments section, below.

 

You can learn more about this from the IBM website:

 

This article was written for IBM i 7.3.

4 comments:

  1. Wow, thanks for the headsup! I am still rocking 7.1 and am dealing with NULLS in restful services today. From what I just read IBM is not getting it right. I would imagine that a field holding memory blanks or zeros should not be indicated as NULL and if you can flip the NULL indicator then that field should lose all its memory~data so it is NULL. What you have shown is very sad indeed.

    ReplyDelete
  2. Hi Damery World. I know this is an old post, but I'm also having problems with a restful api. I'm creating a service that will be called by a vendor's system using JSON. I have created my RPG program using multiple level datastructures (to match the vendor system requirements) and published the service. I can call the service (using SOAPUI rest post with the JSON parameter structure) successfully and everything is almost working perfectly. My problem is that I can't handle the nulls, which will look like {"field",null}. Because of the multiple data structures I can't use the option(*allnull) on the parameter as it's a subfield. Any help would be appreciated. Thanks

    ReplyDelete
  3. By the way, you can use names like _QRNU_NULL_VAR1 in the debugger too, if you want to see or set the null indicators.

    ReplyDelete
  4. I'm on board with this.

    ReplyDelete

To prevent "comment spam" all comments are moderated.
Learn about this website's comments policy here.

Some people have reported that they cannot post a comment using certain computers and browsers. If this is you feel free to use the Contact Form to send me the comment and I will post it for you, please include the title of the post so I know which one to post the comment to.