MQ and Node.js: an update to the TypeScript interface

I recently wrote an article about new TypeScript bindings for the MQ and Node.js interface. Version 0.9.21 of the MQ Node.js interface includes an update to the TypeScript definitions that can assist further in writing correct programs by describing how MQI flags or bitfield parameters are set. Showing how this new capability works was a bit too long to simply add to the original article. So I’ve written this piece.

Bitfield Values

The MQI has a lot of constants that are defined with a value that is a power of 2. Those are often combined to create a single number that is used in fields that provide options for the verb’s behavior. For example, a C program might use the bitwise OR operator in a line such as

gmo.Options = MQGMO_WAIT | MQGMO_NO_SYNCPOINT;

Since the header files have

#define MQGMO_WAIT         0x00000001
#define MQGMO_NO_SYNCPOINT 0x00000004

the resulting value of gmo.Options is 5.

In COBOL programs, the same constants can be used. However, not all implementations of the language support bitwise operators, so the example programs add the numbers together to get the same result.

ADD MQGMO-WAIT MQGMO-NO-SYNCPOINT GIVING MQGMO-OPTIONS.

The problem with using + instead of | is that there are no safeguards against adding a number twice:

ADD MQGMO-WAIT MQGMO-WAIT MQGMO-NO-SYNCPOINT GIVING MQGMO-OPTIONS.

gives 6 which is not the same value as a C program would still have (5) with

MQGMO_WAIT | MQGMO_WAIT | MQGMO_NO_SYNCPOINT

Asides: There are a number of odd or apparently inconsistent things about these MQI flag settings, especially where the zero value is concerned, but this is how it was originally designed so it won’t be changed. And if a verb ever needs more than 32 options, we might be in trouble – the MQGMO set is already very close to that with (I counted) 28 non-zero flags.

TypeScript Array Syntax

In TypeScript (and the underlying JavaScript of course) , you can use the same C-like syntax to combine bits. But there’s a pair of tricks we can play with the interface definition in this language.

  • Permitting an options/flags field to be an array instead of an integer
  • Saying what kinds of elements are allowed in that array

As an example, the MQGMO class now starts with

class MQGMO {
  Options: number | MQC_MQGMO[];
  WaitInterval: number;
  ...

So we can now write

gmo.Options = MQC.MQGMO_NO_SYNCPOINT | MQC.MQGMO_WAIT | MQC.MQGMO_CONVERT;

OR 

gmo.Options = [MQC.MQGMO_NO_SYNCPOINT, MQC.MQGMO_WAIT, MQC.MQGMO_CONVERT];

What is the benefit?

The big benefit here is that you can get compile-time checking for your program, and possibly even code-writing checking or prompting in an IDE.

If I make a mistake and use MQC.MQPMO_NO_SYNCPOINT in the above examples, then the bitwise OR version will not complain. Since the two constants happen to have the same value, the program will even run as expected.

But the same mistake in the array variant will throw up a compilation error:

$ tsc amqsget.ts  
amqsget.ts:70:19 - error TS2322: Type 'MQC_MQPMO' is not assignable to type 'MQC_MQGMO'.  

70 gmo.Options=[MQC.MQPMO_NO_SYNCPOINT,MQC.MQGMO_NO_WAIT,MQC.MQGMO_CONVERT];

Found 1 error.

What is the downside?

The drawback to this syntax is if you have a program that wants to inspect or modify the flags after the MQI verb has been called. The compiler does not understand which type now applies to the field. This example is a little contrived but shows the problem if I wanted to GET one message and change the options for a second message:

gmo.Options = [MQC.MQGMO_NO_SYNCPOINT,MQC.MQGMO_NO_WAIT];

mq.GetSync(hObj, mqmd, gmo, buf, function (err, len) {
  if (!err) {
    // 'push' is the Array operation to add a new element
    gmo.Options.push(MQC.MQGMO_CONVERT);
    ...

$ tsc get.ts
 get.ts:81:19 - error TS2339: Property 'push' does not exist on type 'number | MQC_MQGMO[]'.
   Property 'push' does not exist on type 'number'.
 81 gmo.Options.push(MQC.MQGMO_CONVERT);
                      ~~~~
 Found 1 error.

Instead, I have to make sure the compiler knows which type I’m using:

(gmo.Options as mq.MQC_MQGMO[]).push(MQC.MQGMO_CONVERT);

The related downside is that to do the casting to a specific array type, I have to know what the correct type is. But you can see that in the class definitions and the compiler will also help:

(gmo.Options as mq.MQC_MQPMO[]).push(MQC.MQGMO_CONVERT);

$ tsc get.ts
 get.ts:82:8 - error TS2352: Conversion of type 'number | MQC_MQGMO[]' to type 'MQC_MQPMO[]' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
  Type 'MQC_MQGMO[]' is not comparable to type 'MQC_MQPMO[]'.
  Type 'MQC_MQGMO' is not comparable to type 'MQC_MQPMO'.
 82 (gmo.Options as mq.MQC_MQPMO[]).push(MQC.MQGMO_CONVERT);

Choose numbers or arrays

The rule that has been implemented in this MQI definition is that if you supply an array as input to a verb, it is still an array on output; number in means number out. So you have to decide on your model and stick to it. And you always have to do the casting if you want to inspect or modify a field after calling the verb. Trying to cast an integer to an array or vice versa is likely to result in a runtime error as the compiler accepts your assertion that you know what you are doing.

The dual approach does mean that application code has to be a bit more aware of how a field in the MQI is being used, but changing the options after a call is not done as frequently as the initial setting. The most likely exception is for applications that BROWSE a queue, and the the amqsbra.ts sample has been updated to show how to still work with the bitwise operations:

// First remove original option using a bitwise operation to clear the flag
gmo!.Options = ((gmo!.Options as number) & ~MQC.MQGMO_BROWSE_FIRST);
// And then set the new flag.
gmo!.Options = (gmo!.Options | MQC.MQGMO_BROWSE_NEXT);

One reason for sticking with the OR version here is that JavaScript doesn’t seem to have a convenient way to remove items from an array by value – though you can use a combination of indexOf() and splice(). The ! in this example is an unrelated assertion that by this point in the code, we know that the gmo object cannot be null.

If you do not do the asoperation to make sure the right type is being used, then you will get a compile-time error.

Other languages?

Could we do the same thing for other languages? Probably not for the core procedural languages provided with the MQ product (primarily C, COBOL) if we wanted to maintain any degree of compatibility with 30 years of application and queue manager code. And other newer language interfaces (eg Go) may also be too strongly typed to make it practical – the real fields would likely have to be redefined as an entirely new class. I could imagine a situation where there are two separate fields in a structure, for example Options and OptionsArray, with the wrapper working out which to use, but that seems very unnatural.

I’ve not dug into it, but I suspect that the pymqi libraries might be amenable to this kind of approach.

Summary

The flexibility of the object typing system in TypeScript, built on the non-existence of a system in JavaScript allows provision of a mechanism to help application developers write correct code.

Although this enhancement means that code written to use the initial 0.9.20 level of the TypeScript interface might need to be modified, changes should have been expected and it’s only been a couple of weeks since that release so I imagine there’s not been a huge amount written that would be affected.

I hope this proves to be a useful feature for application developers.

This post was last updated on January 28th, 2022 at 07:43 pm

Leave a Reply

Your email address will not be published. Required fields are marked *