VMS Pascal modernization

Content:

  1. Introduction
  2. Possible scenario
  3. Examples
  4. JSON files
  5. Web Service
  6. Message Queue
    1. ActiveMQ
    2. RabbitMQ
    3. Kafka
  7. Databases
    1. MySQL/MariaDB
    2. SQLRelay (MS SQLServer, Oracle DB, IBM DB2 etc.)

    Introduction:

    OK - so you have some VMS Pascal applications that are important for business.

    Maybe you have worked with VMS Pascal for 40 years. Or maybe you are fresh bachelor in computer science right out of college being tasked with working with VMS Pascal (possible starting with VMS Pascal for C/Java/C# programmers).

    Anyway there is a need to modernize the application a little to integrate better with the rest of the companies IT.

    This article will show some of the options available for that.

    Most of the material has been covered in other articles, but this one has focus on Pascal.

    Possible scenario:

    To illustrate what we are talking about ee will look at a fictive production control system.

    Before modernization:

    Before modernization there are 4 parts of the solution:

    ERP load
    1. ERP system upload a CSV file every night
    2. a special loader program read CSV file and store data in an index-sequentil file
    CRM load
    1. CRM system upload a CSV file every night
    2. a special loader program read CSV file and store data in an index-sequentil file
    core processing
    1. Order system upload a CSV file every night
    2. the processor:
      1. read incoming CSV file
      2. lookup data in ERP index-sequential file and CRM index-sequential file as needed
      3. update production index-sequential file
      4. write outgoing CSV file
    3. CSV file is uploaded to order system
    actual production control
    (which we will consider out of scope for this)
    Before modernization

    It works, but:

    So maybe it could be modernized!

    After modernization:

    After modernization there are 2 parts of the solution:

    core processing
    1. Order system send JSON message to message queue immediatetly
    2. the processor:
      1. receive JSON message from message queue
      2. lookup data in ERP via database lookup and CRM via web service call (JSON/HTTPS) as needed
      3. update production index-sequential file
      4. send JSON message to message queue
    3. Order system receive from message queue
    actual production control
    (which we will consider out of scope for this)
    After modernization

    Now it is real time processing and the flow is actually simpler.

    There are probably not a setup exactly like this anywhere in the world, but there are lots of sites where some of this is happening.

    This article will not show an actual example of such a modernized system - that would require way too much code. This article will just demo some techniques that can be used to build such a modernized system.

    Examples:

    JSON and MQ examples will use this data structure.

    data.inc:

    type
       orderline = record
                      item : varying[32] of char;
                      qty : unsigned;
                      price : unsigned; (* cents *)
                   end;
       order = record
                  id : unsigned;
                  customer : varying[32] of char;
                  status : varying [32] of char;
                  nlines : integer;
                  line : array [1..100] of orderline;
               end;
    
    

    init.inc:

          o[1].id := 1;
          o[1].customer := 'A';
          o[1].status := 'Delivered';
          o[1].nlines := 1;
          o[1].line[1].item := 'X';
          o[1].line[1].qty := 1;
          o[1].line[1].price := 1000;
          o[2].id := 2;
          o[2].customer := 'B';
          o[2].status := 'Delivered';
          o[2].nlines := 1;
          o[2].line[1].item := 'Y';
          o[2].line[1].qty := 2;
          o[2].line[1].price := 2000;
          o[3].id := 3;
          o[3].customer := 'C';
          o[3].status := 'Delivered';
          o[3].nlines := 2;
          o[3].line[1].item := 'Z';
          o[3].line[1].qty := 1;
          o[3].line[1].price := 3000;
          o[3].line[2].item := 'W';
          o[3].line[2].qty := 1;
          o[3].line[2].price := 500;
    

    RESTful web service and database examples will use this data structure:

    t1.inc:

    const
       MAX_ELM = 100;
    
    type
       t1 = record
               f1 : integer;
               f2 : pstr;
            end;
       t1array = record
                    nelm : integer;
                    elm : array [1..MAX_ELM] of t1;
                 end;
    
    procedure dump(o : t1);
    
    begin
       write('(', o.f1:1, ',', o.f2, ')');
    end;
    
    procedure dump_one(o : t1);
    
    begin
       dump(o);
       writeln;
    end;
    
    procedure dump_array(a : t1array);
    
    var
       i : integer;
    
    begin
       for i := 1 to a.nelm do begin
          dump(a.elm[i]);
       end;
       writeln;
    end;
    
    

    Obvious much simpler data than in the real world, but it should be sufficient to show the techniques.

    JSON files:

    If the application is old and it is writing export files or reading import files, then there is a good chance that it is either using fixed width files or CSV files.

    Both formats still works fine, but most newer applications will be using XML or JSON as format.

    So there can be a need to write and read JSON files.

    Writing JSON files:

    JSON is not a complex format and it is possible to write JSON manual.

    Example of manual write.

    writejson_diy.pas:

    program writejson_diy(input,output);
    
    %include 'data.inc'
    
    var
       o : array [1..3] of order;
       i, j : integer;
    
    begin
       %include 'init.inc'
       writeln('[');
       for i := 1 to 3 do begin
          writeln('  {');
          writeln('    "id": ', o[i].id:1, ',');
          writeln('    "customer": "', o[i].customer, '",');
          writeln('    "status": "', o[i].status, '",');
          writeln('    "orderlines": [');
          for j := 1 to o[i].nlines do begin
             writeln('      {');
             writeln('        "item": "', o[i].line[j].item, '",');
             writeln('        "qty": ', o[i].line[j].qty:1, ',');
             writeln('        "price": ', (o[i].line[j].price / 100.00):4:2);
             if j < o[i].nlines then begin
                writeln('      },');
             end else begin
                writeln('      }');
             end;
          end;
          writeln('    ]');
          if i < 3 then begin
             writeln('  },');
          end else begin
             writeln('  }');
          end;
       end;
       writeln(']');
    end.
    

    But to prevent mistakes it can be beneficial to use a JSON library.

    Example using pJSON library.

    writejson_lib.pas:

    [inherit('common','pJSON')]
    program writejson_lib(input,output);
    
    %include 'data.inc'
    
    var
       o : array [1..3] of order;
       i, j : integer;
       orders, neworder, neworderline, temp : cJSON_ptr;
       json : array [1..1000] of pstr;
       n : integer;
       s : pstr;
       
    
    begin
       %include 'init.inc'
       orders := pJSON_CreateArray;
       for i := 1 to 3 do begin
          neworder := pJSON_CreateObject;
          pJSON_AddItemToObject(neworder, 'id',  pJSON_CreateNumber(o[i].id));
          pJSON_AddItemToObject(neworder, 'customer',  pJSON_CreateString(o[i].customer));
          pJSON_AddItemToObject(neworder, 'status',  pJSON_CreateString(o[i].status));
          temp := pJSON_CreateArray;
          for j := 1 to o[i].nlines do begin
             neworderline := pJSON_CreateObject;
             pJSON_AddItemToObject(neworderline, 'item',  pJSON_CreateString(o[i].line[j].item));
             pJSON_AddItemToObject(neworderline, 'qty',  pJSON_CreateNumber(o[i].line[j].qty));
             pJSON_AddItemToObject(neworderline, 'price',  pJSON_CreateNumber(o[i].line[j].price / 100.00));
             pJSON_AddItemReferenceToArray(temp, neworderline);
          end;
          pJSON_AddItemToObject(neworder, 'orderLines',  temp);
          pJSON_AddItemReferenceToArray(orders, neworder);
       end;
       n := pJSON_Print(orders, json);
       for i := 1 to n do begin
          writeln(json[i]);
       end;
       pJSON_Delete(orders);
    end.
    

    Reading JSON files:

    For reading and parsing JSON a library is definitely recommended.

    Example using pJSON library.

    readjson.pas:

    [inherit('common','pJSON')]
    program readjson(input, output);
    
    %include 'data.inc'
    
    const
       MAX_ORDER = 1000;
    
    type
       order_array =  array [1..MAX_ORDER] of order;
    
    procedure load(fnm : pstr; var o : order_array; var n : integer);
    
    var
       f : text;
       json, line : pstr;
       orders, oneorder, orderlines, oneorderline: cJSON_ptr;
       i, j : integer;
    
    begin
       open(f, fnm, old);
       reset(f);
       json := '';
       while not(eof(f)) do begin
          readln(f, line);
          json := json + line;
       end;
       close(f);
       orders := pJSON_Parse(json);
       n := pJSON_GetArraySize(orders);
       for i := 1 to n do begin
          oneorder := pJSON_GetArrayItem(orders, i - 1);
          o[i].id := pJSON_IntValue(pJSON_GetObjectItem(oneorder, 'id'));
          o[i].customer := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'customer'));
          o[i].status := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'status'));
          orderlines := pJSON_GetObjectItem(oneorder, 'orderlines');
          o[i].nlines := pJSON_GetArraySize(orderlines);
          for j := 1 to o[i].nlines do begin
             oneorderline := pJSON_GetArrayItem(orderlines, j - 1);
             o[i].line[j].item := pJSON_StringValue(pJSON_GetObjectItem(oneorderline, 'item'));
             o[i].line[j].qty := pJSON_IntValue(pJSON_GetObjectItem(oneorderline, 'qty'));
             o[i].line[j].price := round(pJSON_DoubleValue(pJSON_GetObjectItem(oneorderline, 'price')) * 100.0);
          end;
       end;
       pJSON_Delete(orders);
    end;
    
    procedure dump(o : order_array; n : integer);
    
    var
       i, j : integer;
    
    begin
       for i := 1 to n do begin
          write('id=', o[i].id:1);
          write(' customer=', o[i].customer);
          writeln(' status=', o[i].status);
          for j := 1 to o[i].nlines do begin
             write('  item=', o[i].line[j].item);
             write(' qty=', o[i].line[j].qty:1);
             writeln(' price=', (o[i].line[j].price / 100.00):4:2);
          end;
       end;
    end;
    
    var
       o : order_array;
       n : integer;
    
    begin
       load('diy.json', o, n);
       dump(o, n);
       load('lib.json', o, n);
       dump(o, n);
    end.
    

    Writing XML files:

    Some flavors of XML can be written manual.

    writexml_diy.pas:

    program writexml_diy(input,output);
    
    %include 'data.inc'
    
    var
       o : array [1..3] of order;
       i, j : integer;
    
    begin
       %include 'init.inc'
       writeln('<orders>');
       for i := 1 to 3 do begin
          writeln('  <order>');
          writeln('    <id>', o[i].id:1, '</id>');
          writeln('    <customer>', o[i].customer, '</customer>');
          writeln('    <status>', o[i].status, '</status>');
          writeln('    <orderlines>');
          for j := 1 to o[i].nlines do begin
             writeln('      <orderline>');
             writeln('        <item>', o[i].line[j].item, '</item>');
             writeln('        <qty>', o[i].line[j].qty:1, '</qty>');
             writeln('        <price>', (o[i].line[j].price / 100.00):4:2, '</price>');
             writeln('      </orderline>');
          end;
          writeln('    </orderlines>');
          writeln('  </order>');
       end;
       writeln('</orders>');
    end.
    

    Reading XML files:

    I am not aware of an easy to use library for VMS Pascal.

    Web Service:

    Web services has been the standard for sync request/response integration for the last 25 years.

    Today the fashion dictates RESTful web services with JSON/HTTP(S) as format and protocol.

    We will test with this example:

    Client:

    Example using phttp library and pJSON library.

    t1json.inc:

    function parse_one(json : cjSON_ptr) : t1;
    
    var
       res : t1;
    
    begin
       res.f1 := pJSON_IntValue(pJSON_GetObjectItem(json, 'f1'));
       res.f2 := pJSON_StringValue(pJSON_GetObjectItem(json, 'f2'));
       parse_one := res;
    end;
    
    function parse_array(json : cJSON_ptr) : t1array;
    
    var
       res : t1array;
       i : integer;
    
    begin
       res.nelm := pJSON_GetArraySize(json);
       for i := 1 to res.nelm do begin
          res.elm[i] := parse_one(pJSON_GetArrayItem(json, i - 1));
       end;
       parse_array := res;
    end;
    
    function format_one(o : t1) : pstr;
    
    var
       newt1 : cJSON_ptr;
       res : pstr;
    
    begin
       newt1 := pJSON_CreateObject;
       pJSON_AddItemToObject(newt1, 'f1',  pJSON_CreateNumber(o.f1));
       pJSON_AddItemToObject(newt1, 'f2',  pJSON_CreateString(o.f2));
       res := pJSON_PrintUnformatted(newt1);
       pJSON_Delete(newt1);
       format_one := res;
    end;
    
    function format_array(a : t1array) : pstr;
    
    var
       newt1array, newt1 : cJSON_ptr;
       res : pstr;
       i : integer;
    
    begin
       newt1array := pJSON_CreateArray;
       for i := 1 to a.nelm do begin
          newt1 := pJSON_CreateObject;
          pJSON_AddItemToObject(newt1, 'f1',  pJSON_CreateNumber(a.elm[i].f1));
          pJSON_AddItemToObject(newt1, 'f2',  pJSON_CreateString(a.elm[i].f2));
          pJSON_AddItemReferenceToArray(newt1array, newt1);
       end;
       res := pJSON_PrintUnformatted(newt1array);
       pJSON_Delete(newt1array);
       format_array := res;
    end;
    
    

    wscli.pas:

    [inherit('common', 'pJSON', 'phttp')]
    program wscli(input,output);
    
    %include 't1.inc'
    %include 't1json.inc'
    
    procedure testGetOne(host : pstr; port : integer; path : pstr; f1 : integer);
    
    var
       con : http;
       fullpath, resp : pstr;
       json : cJSON_ptr;
       o : t1;
    
    begin
       writev(fullpath, path, '/t1/', f1:1);
       con := http_get(fix(host), port, fix(fullpath), 'application/json');
       if (http_numcode(con) div 100) <> 2 then begin
          writeln('HTTP error : ', http_numcode(con):1, ' ', http_txtcode2(con));
       end;
       http_recv_all(con, resp.body, resp.length);
       http_close(con);
       json := pJSON_parse(resp);
       o := parse_one(json);
       pJSON_Delete(json);
       dump_one(o);
    end;
    
    procedure testGetAll(host : pstr; port : integer; path : pstr);
    
    var
       con : http;
       fullpath, resp : pstr;
       json : cJSON_ptr;
       a : t1array;
    
    begin
       writev(fullpath, path, '/t1');
       con := http_get(fix(host), port, fix(fullpath), 'application/json');
       if (http_numcode(con) div 100) <> 2 then begin
          writeln('HTTP error : ', http_numcode(con):1, ' ', http_txtcode2(con));
       end;
       http_recv_all(con, resp.body, resp.length);
       http_close(con);
       json := pJSON_parse(resp);
       a := parse_array(json);
       pJSON_Delete(json);
       dump_array(a);
    end;
    
    procedure testGetSome(host : pstr; port : integer; path : pstr; start_f1, finish_f1 : integer);
    
    var
       con : http;
       fullpath, resp : pstr;
       json : cJSON_ptr;
       a : t1array;
    
    begin
       writev(fullpath, path, '/t1?start=', start_f1:1, '&finish=', finish_f1:1);
       con := http_get(fix(host), port, fix(fullpath), 'application/json');
       if (http_numcode(con) div 100) <> 2 then begin
          writeln('HTTP error : ', http_numcode(con):1, ' ', http_txtcode2(con));
       end;
       http_recv_all(con, resp.body, resp.length);
       http_close(con);
       json := pJSON_parse(resp);
       a := parse_array(json);
       pJSON_Delete(json);
       dump_array(a);
    end;
    
    procedure testPost(host : pstr; port : integer; path : pstr; o : t1);
    
    var
       con : http;
       fullpath, resp : pstr;
       json : cJSON_ptr;
       o2 : t1;
    
    begin
       writev(fullpath, path, '/t1');
       con := http_post(fix(host), port, fix(fullpath), 'application/json', 'application/json', format_one(o));
       if (http_numcode(con) div 100) <> 2 then begin
          writeln('HTTP error : ', http_numcode(con):1, ' ', http_txtcode2(con));
       end;
       http_recv_all(con, resp.body, resp.length);
       http_close(con);
       json := pJSON_parse(resp);
       o2 := parse_one(json);
       pJSON_Delete(json);
       dump_one(o2);
    end;
    
    procedure testPut(host : pstr; port : integer; path : pstr; o : t1);
    
    var
       con : http;
       fullpath : pstr;
    
    begin
       writev(fullpath, path, '/t1/', o.f1:1);
       con := http_put(fix(host), port, fix(fullpath), 'application/json', format_one(o));
       if (http_numcode(con) div 100) <> 2 then begin
          writeln('HTTP error : ', http_numcode(con):1, ' ', http_txtcode2(con));
       end;
       writeln(http_txtcode2(con));
       http_close(con);
    end;
    
    procedure testDelete(host : pstr; port : integer; path : pstr; f1 : integer);
    
    var
       con : http;
       fullpath : pstr;
    
    begin
       writev(fullpath, path, '/t1/', f1:1);
       con := http_delete(fix(host), port, fix(fullpath));
       if (http_numcode(con) div 100) <> 2 then begin
          writeln('HTTP error : ', http_numcode(con):1, ' ', http_txtcode2(con));
       end;
       writeln(http_txtcode2(con));
       http_close(con);
    end;
    
    procedure test(host : pstr; port : integer; path : pstr);
    
    var
       o : t1;
    
    begin
       testGetOne(host, port, path, 123);
       testGetAll(host, port, path);
       testGetSome(host, port, path, 10, 12);
       o.f1 := 123;
       o.f2 := 'ABC';
       testPost(host, port, path, o);
       testPut(host, port, path, o);
       testDelete(host, port, path, 123);
    end;
    
    begin
       test('arnepc5', 81, '/testapi_slim.php');
    end.
    

    Server:

    VMS Pascal is not the optimal language to implement a RESTful service in, but it can be made to work using good old CGI.

    CGI is slow and cumbersome, but it is what we have to work with.

    Example using pJSON library.

    t1json.inc:

    function parse_one(json : cjSON_ptr) : t1;
    
    var
       res : t1;
    
    begin
       res.f1 := pJSON_IntValue(pJSON_GetObjectItem(json, 'f1'));
       res.f2 := pJSON_StringValue(pJSON_GetObjectItem(json, 'f2'));
       parse_one := res;
    end;
    
    function parse_array(json : cJSON_ptr) : t1array;
    
    var
       res : t1array;
       i : integer;
    
    begin
       res.nelm := pJSON_GetArraySize(json);
       for i := 1 to res.nelm do begin
          res.elm[i] := parse_one(pJSON_GetArrayItem(json, i - 1));
       end;
       parse_array := res;
    end;
    
    function format_one(o : t1) : pstr;
    
    var
       newt1 : cJSON_ptr;
       res : pstr;
    
    begin
       newt1 := pJSON_CreateObject;
       pJSON_AddItemToObject(newt1, 'f1',  pJSON_CreateNumber(o.f1));
       pJSON_AddItemToObject(newt1, 'f2',  pJSON_CreateString(o.f2));
       res := pJSON_PrintUnformatted(newt1);
       pJSON_Delete(newt1);
       format_one := res;
    end;
    
    function format_array(a : t1array) : pstr;
    
    var
       newt1array, newt1 : cJSON_ptr;
       res : pstr;
       i : integer;
    
    begin
       newt1array := pJSON_CreateArray;
       for i := 1 to a.nelm do begin
          newt1 := pJSON_CreateObject;
          pJSON_AddItemToObject(newt1, 'f1',  pJSON_CreateNumber(a.elm[i].f1));
          pJSON_AddItemToObject(newt1, 'f2',  pJSON_CreateString(a.elm[i].f2));
          pJSON_AddItemReferenceToArray(newt1array, newt1);
       end;
       res := pJSON_PrintUnformatted(newt1array);
       pJSON_Delete(newt1array);
       format_array := res;
    end;
    
    

    testapi.pas:

    [inherit('common', 'pJSON', 'sys$library:pascal$lib_routines')]
    program testapi(input,output);
    
    %include 't1.inc'
    %include 't1json.inc'
    
    procedure get_one(f1 : integer);
    
    var
       o : t1;
    
    begin
       o.f1 := f1;
       o.f2 := 'getOne';
       writeln('status: 200 OK');
       writeln('content-type: application/json');
       writeln;
       writeln(format_one(o));
    end;
    
    procedure get_all;
    
    var
       a : t1array;
    
    begin
       a.nelm := 3;
       a.elm[1].f1 := 1;
       a.elm[1].f2 := 'getAll #1';
       a.elm[2].f1 := 2;
       a.elm[2].f2 := 'getAll #2';
       a.elm[3].f1 := 3;
       a.elm[3].f2 := 'getAll #3';
       writeln('status: 200 OK');
       writeln('content-type: application/json');
       writeln;
       writeln(format_array(a));
    end;
    
    procedure get_some(start_f1, finish_f1 : integer);
    
    var
       a : t1array;
       i : integer;
    
    begin
       a.nelm := finish_f1 - start_f1 + 1;
       for i := 1 to a.nelm do begin
          a.elm[i].f1 := start_f1 + i - 1;
          writev(a.elm[i].f2, 'getSome #', (start_f1 + i - 1):1);
       end;
       writeln('status: 200 OK');
       writeln('content-type: application/json');
       writeln;
       writeln(format_array(a));
    end;
    
    procedure post(o : t1);
    
    begin
       writeln('status: 200 OK');
       writeln('content-type: application/json');
       writeln;
       writeln(format_one(o));
    end;
    
    procedure put(o : t1);
    
    begin
       writeln('status: 204 No Content');
       writeln;
       writeln('There really is no content here!');
    end;
    
    procedure delete(f1 : integer);
    
    begin
       writeln('status: 204 No Content');
       writeln;
       writeln('There really is no content here!');
    end;
    
    var
       meth, path, qstr, line : pstr;
       f1, start_f1, finish_f1, ix : integer;
       f : text;
       json : cJSON_ptr;
       o : t1;
    
    begin
       lib$get_symbol('REQUEST_METHOD', meth.body, meth.length);
       lib$get_symbol('PATH_INFO', path.body, path.length);
       lib$get_symbol('QUERY_STRING', qstr.body, qstr.length);
       if meth = 'GET' then begin
          if index(path, '/t1/') = 1 then begin
             readv(substr(path, 5, length(path) - 4), f1);
             get_one(f1);
          end else if path = '/t1' then begin
             if qstr = '' then begin
                 get_all;
             end else if index(qstr, 'start=') = 1 then begin
                ix := index(qstr, '&finish=');
                readv(substr(qstr, 7, ix - 7), start_f1);
                readv(substr(qstr, ix + 8, length(qstr) - ix - 7), finish_f1);
                get_some(start_f1, finish_f1);
             end;
          end else begin
             halt;
          end;
       end else if meth = 'POST' then begin
          open(f, 'apache$input', old);
          reset(f);
          readln(f, line);
          close(f);
          json := pJSON_Parse(line);
          o := parse_one(json);
          pJSON_Delete(json);
          post(o);
       end else if meth = 'PUT' then begin
          open(f, 'apache$input', old);
          reset(f);
          readln(f, line);
          close(f);
          json := pJSON_Parse(line);
          o := parse_one(json);
          pJSON_Delete(json);
          put(o);
       end else if meth = 'DELETE' then begin
          readv(substr(path, 5, length(path) - 4), f1);
          delete(f1);
       end else begin
          halt;
       end;
    end.
    

    Message Queue:

    Message queue has been the standard for async push integreation for many years as well.

    For whatever reason it has not really caugth on in the VMS world, but it should be used on VMS as well.

    Note that ActiveMQ is available for VMS (Alpha, Itanium and x86-64).

    Some of the common protocols to talk to message queues are:

    ActiveMQ:

    We will use STOMP for ActiveMQ.

    For other languages than Pascal on VMS see here (examples in Java, C, Python, Fortran, Cobol and Basic).

    Send:

    Example using pstomp library.

    datajson.inc:

    function parse_data(json : pstr; var o : array [low..high:integer] of order) : integer;
    
    var
       orders, oneorder, orderlines, oneorderline: cJSON_ptr;
       i, j, n : integer;
    
    begin
       orders := pJSON_Parse(json);
       n := pJSON_GetArraySize(orders);
       for i := 1 to n do begin
          oneorder := pJSON_GetArrayItem(orders, i - 1);
          o[i].id := pJSON_IntValue(pJSON_GetObjectItem(oneorder, 'id'));
          o[i].customer := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'customer'));
          o[i].status := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'status'));
          orderlines := pJSON_GetObjectItem(oneorder, 'orderlines');
          o[i].nlines := pJSON_GetArraySize(orderlines);
          for j := 1 to o[i].nlines do begin
             oneorderline := pJSON_GetArrayItem(orderlines, j - 1);
             o[i].line[j].item := pJSON_StringValue(pJSON_GetObjectItem(oneorderline, 'item'));
             o[i].line[j].qty := pJSON_IntValue(pJSON_GetObjectItem(oneorderline, 'qty'));
             o[i].line[j].price := round(pJSON_DoubleValue(pJSON_GetObjectItem(oneorderline, 'price')) * 100.0);
          end;
       end;
       pJSON_Delete(orders);
       parse_data := n;
    end;
    
    function format_data(o : array [low..high:integer] of order) : pstr;
    
    var
       i, j : integer;
       orders, neworder, neworderline, temp : cJSON_ptr;
    
    begin
       orders := pJSON_CreateArray;
       for i := lower(o) to upper(o) do begin
          neworder := pJSON_CreateObject;
          pJSON_AddItemToObject(neworder, 'id',  pJSON_CreateNumber(o[i].id));
          pJSON_AddItemToObject(neworder, 'customer',  pJSON_CreateString(o[i].customer));
          pJSON_AddItemToObject(neworder, 'status',  pJSON_CreateString(o[i].status));
          temp := pJSON_CreateArray;
          for j := 1 to o[i].nlines do begin
             neworderline := pJSON_CreateObject;
             pJSON_AddItemToObject(neworderline, 'item',  pJSON_CreateString(o[i].line[j].item));
             pJSON_AddItemToObject(neworderline, 'qty',  pJSON_CreateNumber(o[i].line[j].qty));
             pJSON_AddItemToObject(neworderline, 'price',  pJSON_CreateNumber(o[i].line[j].price / 100.00));
             pJSON_AddItemReferenceToArray(temp, neworderline);
          end;
          pJSON_AddItemToObject(neworder, 'orderLines',  temp);
          pJSON_AddItemReferenceToArray(orders, neworder);
       end;
       format_data := pJSON_PrintUnformatted(orders);
       pJSON_Delete(orders);
    end;
    
    

    send.pas:

    [inherit('common','pjson','pstomp')]
    program send(input,output);
    
    %include 'data.inc'
    %include 'datajson.inc'
    
    var
       ctx : stomp_ctx;
       o : array [1..3] of order;
    
    begin
       %include 'init.inc'
       stomp_debug(0);
       stomp_init(ctx, 'localhost', 61613);
       stomp_write(ctx, 'DemoQ', format_data(o));
       stomp_write(ctx, 'DemoQ', format_data(o));
       stomp_write(ctx, 'DemoQ', format_data(o));
       stomp_write(ctx, 'DemoQ', 'quit');
       stomp_close(ctx);
    end.
    

    Receive:

    Example using pstomp library.

    datajson.inc:

    function parse_data(json : pstr; var o : array [low..high:integer] of order) : integer;
    
    var
       orders, oneorder, orderlines, oneorderline: cJSON_ptr;
       i, j, n : integer;
    
    begin
       orders := pJSON_Parse(json);
       n := pJSON_GetArraySize(orders);
       for i := 1 to n do begin
          oneorder := pJSON_GetArrayItem(orders, i - 1);
          o[i].id := pJSON_IntValue(pJSON_GetObjectItem(oneorder, 'id'));
          o[i].customer := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'customer'));
          o[i].status := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'status'));
          orderlines := pJSON_GetObjectItem(oneorder, 'orderlines');
          o[i].nlines := pJSON_GetArraySize(orderlines);
          for j := 1 to o[i].nlines do begin
             oneorderline := pJSON_GetArrayItem(orderlines, j - 1);
             o[i].line[j].item := pJSON_StringValue(pJSON_GetObjectItem(oneorderline, 'item'));
             o[i].line[j].qty := pJSON_IntValue(pJSON_GetObjectItem(oneorderline, 'qty'));
             o[i].line[j].price := round(pJSON_DoubleValue(pJSON_GetObjectItem(oneorderline, 'price')) * 100.0);
          end;
       end;
       pJSON_Delete(orders);
       parse_data := n;
    end;
    
    function format_data(o : array [low..high:integer] of order) : pstr;
    
    var
       i, j : integer;
       orders, neworder, neworderline, temp : cJSON_ptr;
    
    begin
       orders := pJSON_CreateArray;
       for i := lower(o) to upper(o) do begin
          neworder := pJSON_CreateObject;
          pJSON_AddItemToObject(neworder, 'id',  pJSON_CreateNumber(o[i].id));
          pJSON_AddItemToObject(neworder, 'customer',  pJSON_CreateString(o[i].customer));
          pJSON_AddItemToObject(neworder, 'status',  pJSON_CreateString(o[i].status));
          temp := pJSON_CreateArray;
          for j := 1 to o[i].nlines do begin
             neworderline := pJSON_CreateObject;
             pJSON_AddItemToObject(neworderline, 'item',  pJSON_CreateString(o[i].line[j].item));
             pJSON_AddItemToObject(neworderline, 'qty',  pJSON_CreateNumber(o[i].line[j].qty));
             pJSON_AddItemToObject(neworderline, 'price',  pJSON_CreateNumber(o[i].line[j].price / 100.00));
             pJSON_AddItemReferenceToArray(temp, neworderline);
          end;
          pJSON_AddItemToObject(neworder, 'orderLines',  temp);
          pJSON_AddItemReferenceToArray(orders, neworder);
       end;
       format_data := pJSON_PrintUnformatted(orders);
       pJSON_Delete(orders);
    end;
    
    

    recv.pas:

    [inherit('common','pstomp','pjson')]
    program recv(input,output);
    
    %include 'data.inc'
    %include 'datajson.inc'
    
    var
       ctx : stomp_ctx;
       msg : pstr;
       msglen : integer;
       done : boolean;
       o : array [1..100] of order;
       i, j, n : integer;
    
    begin
       stomp_debug(0);
       stomp_init(ctx, 'localhost', 61613);
       done := false;
       repeat
          stomp_read(ctx, 'DemoQ', msg.body, msglen);
          if msg.body[msglen] = chr(10) then msglen := msglen - 1;
          msg.length := msglen;
          if msg = 'quit' then begin
             done := true;
          end else begin
             n := parse_data(msg, o);
             for i := 1 to n do begin
                write('id=', o[i].id:1);
                write(' customer=', o[i].customer);
                writeln(' status=', o[i].status);
                for j := 1 to o[i].nlines do begin
                   write('  item=', o[i].line[j].item);
                   write(' qty=', o[i].line[j].qty:1);
                   writeln(' price=', (o[i].line[j].price / 100.00):4:2);
                end;
             end;
          end;
       until done;
       stomp_close(ctx);
    end.
    

    RabbitMQ:

    VSI provide a port of librabbitmq with VMS Pascal support.

    Send:

    datajson.inc:

    function parse_data(json : pstr; var o : array [low..high:integer] of order) : integer;
    
    var
       orders, oneorder, orderlines, oneorderline: cJSON_ptr;
       i, j, n : integer;
    
    begin
       orders := pJSON_Parse(json);
       n := pJSON_GetArraySize(orders);
       for i := 1 to n do begin
          oneorder := pJSON_GetArrayItem(orders, i - 1);
          o[i].id := pJSON_IntValue(pJSON_GetObjectItem(oneorder, 'id'));
          o[i].customer := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'customer'));
          o[i].status := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'status'));
          orderlines := pJSON_GetObjectItem(oneorder, 'orderlines');
          o[i].nlines := pJSON_GetArraySize(orderlines);
          for j := 1 to o[i].nlines do begin
             oneorderline := pJSON_GetArrayItem(orderlines, j - 1);
             o[i].line[j].item := pJSON_StringValue(pJSON_GetObjectItem(oneorderline, 'item'));
             o[i].line[j].qty := pJSON_IntValue(pJSON_GetObjectItem(oneorderline, 'qty'));
             o[i].line[j].price := round(pJSON_DoubleValue(pJSON_GetObjectItem(oneorderline, 'price')) * 100.0);
          end;
       end;
       pJSON_Delete(orders);
       parse_data := n;
    end;
    
    function format_data(o : array [low..high:integer] of order) : pstr;
    
    var
       i, j : integer;
       orders, neworder, neworderline, temp : cJSON_ptr;
    
    begin
       orders := pJSON_CreateArray;
       for i := lower(o) to upper(o) do begin
          neworder := pJSON_CreateObject;
          pJSON_AddItemToObject(neworder, 'id',  pJSON_CreateNumber(o[i].id));
          pJSON_AddItemToObject(neworder, 'customer',  pJSON_CreateString(o[i].customer));
          pJSON_AddItemToObject(neworder, 'status',  pJSON_CreateString(o[i].status));
          temp := pJSON_CreateArray;
          for j := 1 to o[i].nlines do begin
             neworderline := pJSON_CreateObject;
             pJSON_AddItemToObject(neworderline, 'item',  pJSON_CreateString(o[i].line[j].item));
             pJSON_AddItemToObject(neworderline, 'qty',  pJSON_CreateNumber(o[i].line[j].qty));
             pJSON_AddItemToObject(neworderline, 'price',  pJSON_CreateNumber(o[i].line[j].price / 100.00));
             pJSON_AddItemReferenceToArray(temp, neworderline);
          end;
          pJSON_AddItemToObject(neworder, 'orderLines',  temp);
          pJSON_AddItemReferenceToArray(orders, neworder);
       end;
       format_data := pJSON_PrintUnformatted(orders);
       pJSON_Delete(orders);
    end;
    
    

    sendr.pas:

    [inherit('common', 'pjson', 'librabbitmq$root:[include]amqpdef')]
    program sendr(input, output);
    
    %include 'data.inc'
    %include 'datajson.inc'
    
    procedure check_status(lbl : pstr; stat : integer);
    
    begin
       if not odd(stat) then begin
          writeln(lbl, ': ', stat);
          halt;
       end;
    end;
    
    var
       h : integer;
       stat : integer;
       o : array [1..3] of order;
       buf : pstr;
    
    begin
       %include 'init.inc'
       stat := amqp$new_connection(h, 'arnepc5', 5672);
       check_status('new connection', stat);
       stat := amqp$login(h, '/', 0, 65535, 0, amqp$l_sasl_method_plain, 'arne', 'hemmeligt');
       check_status('login', stat);
       stat := amqp$channel_open(h, 1);
       check_status('channel open', stat);
       stat := amqp$queue_bind(h, 1, 'TestQ', 'amq.direct', 'key0', 0);
       check_status('queue bind', stat);
       buf := format_data(o);
       stat := amqp$basic_publish(h, 1, 'amq.direct', 'key0', 1, 0, 0, iaddress(buf.body), buf.length);
       check_status('basic publish', stat);
       buf := format_data(o);
       stat := amqp$basic_publish(h, 1, 'amq.direct', 'key0', 1, 0, 0, iaddress(buf.body), buf.length);
       check_status('basic publish', stat);
       buf := format_data(o);
       stat := amqp$basic_publish(h, 1, 'amq.direct', 'key0', 1, 0, 0, iaddress(buf.body), buf.length);
       check_status('basic publish', stat);
       buf := 'quit';
       stat := amqp$basic_publish(h, 1, 'amq.direct', 'key0', 1, 0, 0, iaddress(buf.body), buf.length);
       check_status('basic publish', stat);
       stat := amqp$channel_close(h, 1, amqp$l_reply_success);
       check_status('channle close', stat);
       stat := amqp$connection_close(h, amqp$l_reply_success);
       check_status('connection close', stat);
       stat := amqp$destroy_connection(h);
       check_status('destroy connection', stat);
    end.
    

    Receive:

    datajson.inc:

    function parse_data(json : pstr; var o : array [low..high:integer] of order) : integer;
    
    var
       orders, oneorder, orderlines, oneorderline: cJSON_ptr;
       i, j, n : integer;
    
    begin
       orders := pJSON_Parse(json);
       n := pJSON_GetArraySize(orders);
       for i := 1 to n do begin
          oneorder := pJSON_GetArrayItem(orders, i - 1);
          o[i].id := pJSON_IntValue(pJSON_GetObjectItem(oneorder, 'id'));
          o[i].customer := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'customer'));
          o[i].status := pJSON_StringValue(pJSON_GetObjectItem(oneorder, 'status'));
          orderlines := pJSON_GetObjectItem(oneorder, 'orderlines');
          o[i].nlines := pJSON_GetArraySize(orderlines);
          for j := 1 to o[i].nlines do begin
             oneorderline := pJSON_GetArrayItem(orderlines, j - 1);
             o[i].line[j].item := pJSON_StringValue(pJSON_GetObjectItem(oneorderline, 'item'));
             o[i].line[j].qty := pJSON_IntValue(pJSON_GetObjectItem(oneorderline, 'qty'));
             o[i].line[j].price := round(pJSON_DoubleValue(pJSON_GetObjectItem(oneorderline, 'price')) * 100.0);
          end;
       end;
       pJSON_Delete(orders);
       parse_data := n;
    end;
    
    function format_data(o : array [low..high:integer] of order) : pstr;
    
    var
       i, j : integer;
       orders, neworder, neworderline, temp : cJSON_ptr;
    
    begin
       orders := pJSON_CreateArray;
       for i := lower(o) to upper(o) do begin
          neworder := pJSON_CreateObject;
          pJSON_AddItemToObject(neworder, 'id',  pJSON_CreateNumber(o[i].id));
          pJSON_AddItemToObject(neworder, 'customer',  pJSON_CreateString(o[i].customer));
          pJSON_AddItemToObject(neworder, 'status',  pJSON_CreateString(o[i].status));
          temp := pJSON_CreateArray;
          for j := 1 to o[i].nlines do begin
             neworderline := pJSON_CreateObject;
             pJSON_AddItemToObject(neworderline, 'item',  pJSON_CreateString(o[i].line[j].item));
             pJSON_AddItemToObject(neworderline, 'qty',  pJSON_CreateNumber(o[i].line[j].qty));
             pJSON_AddItemToObject(neworderline, 'price',  pJSON_CreateNumber(o[i].line[j].price / 100.00));
             pJSON_AddItemReferenceToArray(temp, neworderline);
          end;
          pJSON_AddItemToObject(neworder, 'orderLines',  temp);
          pJSON_AddItemReferenceToArray(orders, neworder);
       end;
       format_data := pJSON_PrintUnformatted(orders);
       pJSON_Delete(orders);
    end;
    
    

    recvr.pas:

    [inherit('common', 'pjson', 'librabbitmq$root:[include]amqpdef')]
    program recvr(input, output);
    
    %include 'data.inc'
    %include 'datajson.inc'
    
    procedure check_status(lbl : pstr; stat : integer);
    
    begin
            if not odd(stat) then begin
          writeln(lbl, ': ', stat);
          halt;
       end;
    end;
    
    var
       h : integer;
       stat : integer;
       auto_ack : integer;
       tag : integer64;
       msg : pstr;
       msglen : integer;
       done : boolean;
       o : array [1..100] of order;
       i, j, n : integer;
    
    begin
       stat := amqp$new_connection(h, 'arnepc5', 5672);
       check_status('new connection', stat);
       stat := amqp$login(h, '/', 0, 65535, 0, amqp$l_sasl_method_plain, 'arne', 'hemmeligt');
       check_status('login', stat);
       stat := amqp$channel_open(h, 1);
       check_status('channel open', stat);
       auto_ack := 1;
       stat := amqp$basic_consume(h, 1, 'TestQ', 0, 0, auto_ack, 0, 0);
       check_status('basic consume', stat);
       done := false;
       repeat
          stat := amqp$dequeue(h, msg.body, 32000, msglen, 0, 0, 0, tag);
          msg.length := msglen;
          if msg = 'quit' then begin
             done := true;
          end else begin
             n := parse_data(msg, o);
             for i := 1 to n do begin
                write('id=', o[i].id:1);
                write(' customer=', o[i].customer);
                writeln(' status=', o[i].status);
                for j := 1 to o[i].nlines do begin
                   write('  item=', o[i].line[j].item);
                   write(' qty=', o[i].line[j].qty:1);
                   writeln(' price=', (o[i].line[j].price / 100.00):4:2);
                end;
             end;
          end;
       until done;
       stat := amqp$channel_close(h, 1, amqp$l_reply_success);
       check_status('channel close', stat);
       stat := amqp$connection_close(h, amqp$l_reply_success);
       check_status('connection close', stat);
       stat := amqp$destroy_connection(h);
       check_status('destroy connection', stat);
    end.
    

    Kafka:

    VSI provide a port of librdkafka.

    I have created a small VMS Pascal wrapper pkafka.

    Send:

    sendk.pas:

    [inherit('common','pjson','pkafka')]
    program sendk(input,output);
    
    %include 'data.inc'
    %include 'datajson.inc'
    
    var
       conf : kconf_h;
       prod : kprod_h;
       o : array [1..3] of order;
       key : packed array [1..4] of char value chr(0) + chr(0) + chr(0) + chr(0);
    
    begin
       %include 'init.inc'
       conf :=  pkafka_init('arnepc5:9092');
       prod := pkafka_create_prod(conf, 'DemoQ');
       pkafka_send(prod, key, format_data(o));
       pkafka_send(prod, key, format_data(o));
       pkafka_send(prod, key, format_data(o));
       pkafka_send(prod, key, 'quit');
       pkafka_destroy_prod(prod);
    end.
    

    Receive:

    The librdkafka receive API is not friendly for other languages than C, so no Kafka receive.

    Databases:

    Relational databases are the standard for persistence of business data today.

    Unfortunatetly the situation for relational databases on VMS is a bit problemartic.

    Rdb has fine VMS Pascal support, but Rdb will not be ported to VMS x86-64 and support for Rdb on VMS Alpha and VMS Itanium will run out soon.

    Mimer is available on VMS, but does not offer VMS Pascal support.

    MySQL/MariaDB is available on VMS Alpha and VMS Itanium but not on VMS x86-64 yet.

    So very likely the VMS Pascal application will be accessing a relational database on another platform.

    MySQL:

    Example using pmysql library.

    t1json.inc:

    function parse_one(json : cjSON_ptr) : t1;
    
    var
       res : t1;
    
    begin
       res.f1 := pJSON_IntValue(pJSON_GetObjectItem(json, 'f1'));
       res.f2 := pJSON_StringValue(pJSON_GetObjectItem(json, 'f2'));
       parse_one := res;
    end;
    
    function parse_array(json : cJSON_ptr) : t1array;
    
    var
       res : t1array;
       i : integer;
    
    begin
       res.nelm := pJSON_GetArraySize(json);
       for i := 1 to res.nelm do begin
          res.elm[i] := parse_one(pJSON_GetArrayItem(json, i - 1));
       end;
       parse_array := res;
    end;
    
    function format_one(o : t1) : pstr;
    
    var
       newt1 : cJSON_ptr;
       res : pstr;
    
    begin
       newt1 := pJSON_CreateObject;
       pJSON_AddItemToObject(newt1, 'f1',  pJSON_CreateNumber(o.f1));
       pJSON_AddItemToObject(newt1, 'f2',  pJSON_CreateString(o.f2));
       res := pJSON_PrintUnformatted(newt1);
       pJSON_Delete(newt1);
       format_one := res;
    end;
    
    function format_array(a : t1array) : pstr;
    
    var
       newt1array, newt1 : cJSON_ptr;
       res : pstr;
       i : integer;
    
    begin
       newt1array := pJSON_CreateArray;
       for i := 1 to a.nelm do begin
          newt1 := pJSON_CreateObject;
          pJSON_AddItemToObject(newt1, 'f1',  pJSON_CreateNumber(a.elm[i].f1));
          pJSON_AddItemToObject(newt1, 'f2',  pJSON_CreateString(a.elm[i].f2));
          pJSON_AddItemReferenceToArray(newt1array, newt1);
       end;
       res := pJSON_PrintUnformatted(newt1array);
       pJSON_Delete(newt1array);
       format_array := res;
    end;
    
    

    dbmysql.pas:

    [inherit('common', 'mysql', 'pmysql', 'pmysql2')]
    program mysql(input, output);
    
    %include 't1.inc'
    
    procedure checkcon(con : mysql_ptr);
    
    begin
       if con = 0 then begin
          writeln(pmysql_error(con));
          halt;
       end;
    end;
    
    procedure checkstmt(stmt : mysql_stmt_ptr; con : mysql_ptr);
    
    begin
       if stmt = 0 then begin
          writeln(pmysql_error(con));
          halt;
       end;
    end;
    
    procedure checkstat(stat : integer; stmt : mysql_stmt_ptr);
    
    begin
       if stat <> 0 then begin
          writeln(pmysql_stmt_error(stmt));
          halt;
       end;
    end;
    
    function connect(host, un, pw, db : pstr) : mysql_ptr;
    
    var
       con : mysql_ptr;
    
    begin
       con := pmysql_init;
       checkcon(con);
       con := pmysql_real_connect(con, host, un, pw, db);
       checkcon(con);
       connect := con;
    end;
    
    function t1_get_one(con : mysql_ptr; f2 : pstr) : integer;
    
    var
       stmt : mysql_stmt_ptr;
       stat : integer;
       inparam : array[1..1] of mysql_bind;
       outparam : array[1..1] of mysql_bind;
       f1 : integer;
    
    begin
       stmt := pmysql_stmt_init(con);
       checkstmt(stmt, con);
       stat := pmysql_stmt_prepare(stmt, 'SELECT f1 FROM t1 WHERE f2 = ?');
       checkstat(stat, stmt);
       pmysql_init_bind_string_in(inparam[1], f2);
       stat := pmysql_stmt_bind_param(stmt, inparam);
       checkstat(stat, stmt);
       stat := pmysql_stmt_execute(stmt);
       checkstat(stat, stmt);
       pmysql_init_bind_long(outparam[1], f1);
       stat := pmysql_stmt_bind_result(stmt, outparam);
       checkstat(stat, stmt);
       stat := pmysql_stmt_store_result(stmt);
       checkstat(stat, stmt);
       if pmysql_stmt_fetch(stmt) = 0 then begin
          t1_get_one := f1;
       end else begin
          writeln('Row not found');
          halt;
       end;
       pmysql_stmt_free_result(stmt);
    end;
    
    function t1_get_all(con : mysql_ptr; var buf : array[$L1..$U1:integer] of t1; bufsiz : integer) : integer;
    
    var
       stmt : mysql_stmt_ptr;
       stat : integer;
       outparam : array[1..2] of mysql_bind;
       f1 : integer;
       f2 : longpstr(255);
       count : integer;
    
    begin
       stmt := pmysql_stmt_init(con);
       checkstmt(stmt, con);
       stat := pmysql_stmt_prepare(stmt, 'SELECT f1,f2 FROM t1');
       checkstat(stat, stmt);
       stat := pmysql_stmt_execute(stmt);
       checkstat(stat, stmt);
       pmysql_init_bind_long(outparam[1], f1);
       pmysql_init_bind_string_out(outparam[2], f2);
       stat := pmysql_stmt_bind_result(stmt, outparam);
       checkstat(stat, stmt);
       stat := pmysql_stmt_store_result(stmt);
       checkstat(stat, stmt);
       count := 0;
       while pmysql_stmt_fetch(stmt) = 0 do begin
          count := count + 1;
          buf[count].f1 := f1;
          buf[count].f2 := stdstr(f2);
       end;
       pmysql_stmt_free_result(stmt);
       t1_get_all := count;
    end;
    
    procedure t1_put(con : mysql_ptr; f1 : integer; f2 : pstr);
    
    var
       stmt : mysql_stmt_ptr;
       stat : integer;
       inparam : array[1..2] of mysql_bind;
    
    begin
       stmt := pmysql_stmt_init(con);
       checkstmt(stmt, con);
       stat := pmysql_stmt_prepare(stmt, 'INSERT INTO t1 VALUES(?, ?)');
       checkstat(stat, stmt);
       pmysql_init_bind_long(inparam[1], f1);
       pmysql_init_bind_string_in(inparam[2], f2);
       stat := pmysql_stmt_bind_param(stmt, inparam);
       checkstat(stat, stmt);
       stat := pmysql_stmt_execute(stmt);
       checkstat(stat, stmt);
       if pmysql_stmt_affected_rows(stmt) <> 1 then begin
          writeln('INSERT did not insert 1 row');
          halt;
       end;
       pmysql_stmt_free_result(stmt);
    end;
    
    procedure t1_remove(con : mysql_ptr; f1 : integer);
    
    var
       stmt : mysql_stmt_ptr;
       stat : integer;
       inparam : array[1..1] of mysql_bind;
    
    begin
       stmt := pmysql_stmt_init(con);
       checkstmt(stmt, con);
       stat := pmysql_stmt_prepare(stmt, 'DELETE FROM t1 WHERE f1 = ?');
       checkstat(stat, stmt);
       pmysql_init_bind_long(inparam[1], f1);
       stat := pmysql_stmt_bind_param(stmt, inparam);
       checkstat(stat, stmt);
       stat := pmysql_stmt_execute(stmt);
       checkstat(stat, stmt);
       if pmysql_stmt_affected_rows(stmt) <> 1 then begin
          writeln('DELETE did not delete 1 row');
          halt;
       end;
       pmysql_stmt_free_result(stmt);
    end;
    
    procedure t1_dump(con : mysql_ptr);
    
    const
       MAX_REC = 100;
    
    var
       buf : array [1..MAX_REC] of t1;
       i, n : integer;
    
    begin
       n := t1_get_all(con, buf, MAX_REC);
       for i := 1 to n do begin
          writeln('  ', buf[i].f1:1, ' ', buf[i].f2);
       end;
    end;
    
    var
       con : mysql_ptr;
       f1 : integer;
    
    begin
       con := connect('localhost', 'root', '', 'test');
       f1 := t1_get_one(con, 'BB');
       writeln('one:');
       writeln('  ', f1:1);
       writeln('all:');
       t1_dump(con);   
       t1_put(con, 999, 'XXX');
       writeln('all after insert:');
       t1_dump(con);
       t1_remove(con, 999);
       writeln('all after delete:');
       t1_dump(con);
       pmysql_close(con);
    end.
    
    

    SQLRelay (MS SQLServer, Oracle DB, IBM DB2 etc.):

    SQLRelay for VMS is a VMS client and a Linux/Windows proxy allowing a VMS application to connect to any database that has a Linux/Windows database driver (and that is practically all databases!).

    SQLRelay client is open source ported to VMS and available from VSI.

    I go through SQLRelay in detail in VMS Tech Demo 2 - SQL Relay.

    The Pascal code shown here is almost identical to the Pascal code in that article.

    Here we will demo access the following databases:

    sqlr.conf:

    <?xml version="1.0"?>
    <instances>
        <instance id="odbcsqlsrv" port="9001" dbase="odbc">
            <users>
                <user user="arne" password="topsecret"/>
            </users>
            <connections>
                <connection string="dsn=ARNEPC5_SQLServer"/>
            </connections>
        </instance>
        <instance id="odbcdb2" port="9002" dbase="odbc">
            <users>
                <user user="arne" password="topsecret"/>
            </users>
            <connections>
                <connection string="dsn=ARNEPC5_DB2;user=arne;password=hemmeligt"/>
            </connections>
        </instance>
        <instance id="localora" port="9003" dbase="oracle">
            <users>
                <user user="arne" password="topsecret"/>
            </users>
            <connections>
                <connection string="oracle_sid=(DESCRIPTION = (ADDRESS = (PROTOCOL = TCP)(HOST = localhost)(PORT = 1521)) (CONNECT_DATA = (SERVER = DEDICATED) (SERVICE_NAME = Test)));user=arne;password=hemmeligt"/>
            </connections>
        </instance>
        <instance id="localdb2" port="9004" dbase="db2" connections="1" maxconnections="1">
            <users>
                <user user="arne" password="topsecret"/>
            </users>
            <connections>
                <connection string="host=localhost;db=Test;user=arne;password=hemmeligt"/>
            </connections>
        </instance>
        <instance id="localmysql" port="9005" dbase="mysql">
            <users>
                <user user="arne" password="topsecret"/>
            </users>
            <connections>
                <connection string="host=localhost;db=Test;user=arne;password=hemmeligt"/>
            </connections>
        </instance>
        <instance id="localpgsql" port="9006" dbase="postgresql">
            <users>
                <user user="arne" password="topsecret"/>
            </users>
            <connections>
                <connection string="host=localhost;db=Test;user=postgres;password=hemmeligt"/>
            </connections>
        </instance>
    </instances>
    

    sqlrstart.bat:

    sqlr-start -config sqlr.conf -id odbcsqlsrv
    sqlr-start -config sqlr.conf -id odbcdb2
    sqlr-start -config sqlr.conf -id localora
    sqlr-start -config sqlr.conf -id localdb2
    sqlr-start -config sqlr.conf -id localmysql
    sqlr-start -config sqlr.conf -id localpgsql
    

    sqlrstop.bat:

    sqlr-stop -config sqlr.conf -id odbcsqlsrv
    sqlr-stop -config sqlr.conf -id odbcdb2
    sqlr-stop -config sqlr.conf -id localora
    sqlr-stop -config sqlr.conf -id localdb2
    sqlr-stop -config sqlr.conf -id localmysql
    sqlr-stop -config sqlr.conf -id localpgsql
    

    t1json.inc:

    function parse_one(json : cjSON_ptr) : t1;
    
    var
       res : t1;
    
    begin
       res.f1 := pJSON_IntValue(pJSON_GetObjectItem(json, 'f1'));
       res.f2 := pJSON_StringValue(pJSON_GetObjectItem(json, 'f2'));
       parse_one := res;
    end;
    
    function parse_array(json : cJSON_ptr) : t1array;
    
    var
       res : t1array;
       i : integer;
    
    begin
       res.nelm := pJSON_GetArraySize(json);
       for i := 1 to res.nelm do begin
          res.elm[i] := parse_one(pJSON_GetArrayItem(json, i - 1));
       end;
       parse_array := res;
    end;
    
    function format_one(o : t1) : pstr;
    
    var
       newt1 : cJSON_ptr;
       res : pstr;
    
    begin
       newt1 := pJSON_CreateObject;
       pJSON_AddItemToObject(newt1, 'f1',  pJSON_CreateNumber(o.f1));
       pJSON_AddItemToObject(newt1, 'f2',  pJSON_CreateString(o.f2));
       res := pJSON_PrintUnformatted(newt1);
       pJSON_Delete(newt1);
       format_one := res;
    end;
    
    function format_array(a : t1array) : pstr;
    
    var
       newt1array, newt1 : cJSON_ptr;
       res : pstr;
       i : integer;
    
    begin
       newt1array := pJSON_CreateArray;
       for i := 1 to a.nelm do begin
          newt1 := pJSON_CreateObject;
          pJSON_AddItemToObject(newt1, 'f1',  pJSON_CreateNumber(a.elm[i].f1));
          pJSON_AddItemToObject(newt1, 'f2',  pJSON_CreateString(a.elm[i].f2));
          pJSON_AddItemReferenceToArray(newt1array, newt1);
       end;
       res := pJSON_PrintUnformatted(newt1array);
       pJSON_Delete(newt1array);
       format_array := res;
    end;
    
    

    dbremote.pas:

    [inherit('common')]
    program dbremote(input, output);
    
    %include "sqlr$pascal:sqlrdef.pas"
    
    %include 't1.inc'
    
    type
       sqlrcon = integer64;
       sqlrcurs = integer64;
    
    procedure con_exit(con : sqlrcon);
    
    var
       msg : pstr;
    
    begin
       sqlr$con_errormessage(con, msg.body, msg.length);
       writeln('SQLR connection error: ' + msg);
       halt;
    end;
    
    procedure curs_exit(curs : sqlrcurs);
    
    var
       msg : pstr;
    
    begin
       sqlr$cur_errormessage(curs, msg.body, msg.length);
       writeln('SQLR cursor error: ' + msg);
       halt;
    end;
    
    procedure other_exit(msg : pstr);
    
    begin
       writeln(msg);
       halt;
    end;
    
    function t1_get_one(con : sqlrcon;
                        f2 : pstr;
                        function ph(num, nam, typ : pstr) : pstr;
                        function bn(num, nam : pstr) : pstr) : integer;
    
    var
       curs : sqlrcurs;
       temp : integer64;
       stat, res, f1 : integer;
       s : pstr;
    
    begin
       sqlr$cur_alloc(con, curs);
       sqlr$cur_preparequery(curs, 'SELECT f1 FROM t1 WHERE f2 = ' + ph('1', 'f2', 'text'));
       sqlr$cur_inputbindstring(curs, bn('1', 'f2'), substr(f2.body, 1, f2.length));
       stat := sqlr$cur_executequery(curs, res);
       if (stat mod 2) = 0 then curs_exit(curs);
       stat := sqlr$cur_getfieldbyindex(curs, 0, 0, s.body, s.length);
       if (stat mod 2) = 0 then begin
          other_exit('Row not found');
       end;
       sqlr$cur_getfieldasintbyind(curs, 0, 0, temp);
       f1 := temp;
       sqlr$con_endsession(con);
       sqlr$cur_free(curs);
       t1_get_one := f1;
    end;
    
    function t1_get_all(con : sqlrcon;
                        var buf : array[$L1..$U1:integer] of t1;
                        bufsiz : integer) : integer;
    
    label
       done;
    
    var
       curs : sqlrcurs;
       stat, count : integer;
       row : unsigned64;
       temp : integer64;
       s : pstr;
    
    begin
       sqlr$cur_alloc(con, curs);
       stat := sqlr$cur_sendquery(curs, 'select f1,f2 from t1');
       if (stat mod 2) = 0 then curs_exit(curs);
       sqlr$cur_setresultsetbuffersize(curs, 100);
       row := 0;
       count := 0;
       while true do begin
          if count >= bufsiz then goto done;
          stat := sqlr$cur_getfieldbyindex(curs, row, 0, s.body, s.length);
          if (stat mod 2) = 0 then goto done;
          count := count + 1;
          sqlr$cur_getfieldasintbyind(curs, row, 0, temp);
          buf[count].f1 := temp;
          sqlr$cur_getfieldbyindex(curs, row, 1, buf[count].f2.body, buf[count].f2.length);
          row := row + 1;
       end;
    done:
       sqlr$con_endsession(con);
       sqlr$cur_free(curs);
       t1_get_all := count;
    end;
    
    procedure t1_put(con : sqlrcon;
                     f1 : integer;
                     f2 : pstr; function ph(num, nam, typ : pstr) : pstr;
                     function bn(num, nam : pstr) : pstr);
    
    var
       curs : sqlrcurs;
       stat, res : integer;
       temp : integer64;
       n : unsigned64;
    
    begin
       sqlr$cur_alloc(con, curs);
       sqlr$cur_preparequery(curs, 'INSERT INTO t1 VALUES(' + ph('1', 'f1', 'int') + ', ' + ph('2', 'f2', 'text') + ')');
       temp := f1;
       sqlr$cur_inputbindlong(curs, bn('1', 'f1'), temp);
       sqlr$cur_inputbindstring(curs, bn('2', 'f2'), substr(f2.body, 1, f2.length));
       stat := sqlr$cur_executequery(curs, res);
       if (stat mod 2) = 0 then curs_exit(curs);
       sqlr$cur_affectedrows(curs, n);
       if n <> 1 then begin
          other_exit('INSERT did not insert 1 row');
       end;
       sqlr$con_endsession(con);
       sqlr$cur_free(curs);
    end;
    
    procedure t1_remove(con : sqlrcon;
                        f1 : integer;
                        function ph(num, nam, typ : pstr) : pstr;
                        function bn(num, nam : pstr) : pstr);
    
    var
       curs : sqlrcurs;
       stat, res : integer;
       temp : integer64;
       n : unsigned64;
    
    begin
       sqlr$cur_alloc(con, curs);
       sqlr$cur_preparequery(curs, 'DELETE FROM t1 WHERE f1 = ' + ph('1', 'f1', 'int'));
       temp := f1;
       sqlr$cur_inputbindlong(curs, bn('1', 'f1'), temp);
       stat := sqlr$cur_executequery(curs, res);
       if (stat mod 2) = 0 then curs_exit(curs);
       sqlr$cur_affectedrows(curs, n);
       if n <> 1 then begin
          other_exit('DELETE did not delete 1 row');
       end;
       sqlr$con_endsession(con);
       sqlr$cur_free(curs);
    end;
    
    procedure t1_dump(con : sqlrcon);
    
    const
       MAX_REC = 100;
    
    var
       buf : array [1..MAX_REC] of t1;
       i, n : integer;
    
    begin
       n := t1_get_all(con, buf, MAX_REC);
       for i := 1 to n do begin
          writeln('  ', buf[i].f1:1, ' ', buf[i].f2);
       end;
    end;
    
    procedure test(lbl : pstr;
                   host : pstr;
                   port : unsigned16;
                   un : pstr;
                   pw : pstr;
                   function ph(num, nam, typ : pstr) : pstr;
                   function bn(num, nam : pstr) : pstr);
    
    var
       con : sqlrcon;
       socket : packed array[1..1] of char value '?';
       f1 : integer;
    
    begin
       writeln('Connect host=', host, ' port=', port:1, ' (', lbl, ')'); 
       sqlr$con_alloc(fix(host), port, socket, fix(un), fix(pw), 0, 1, con);
       f1 := t1_get_one(con, 'BB', ph, bn);
       writeln('one:');
       writeln('  ', f1:1);
       writeln('all:');
       t1_dump(con);   
       t1_put(con, 999, 'XXX', ph, bn);
       writeln('all after insert:');
       t1_dump(con);
       t1_remove(con, 999, ph, bn);
       writeln('all after delete:');
       t1_dump(con);
       sqlr$con_free(con);
    end;
    
    function std_ph(num, nam, typ : pstr) : pstr;
    
    begin
       std_ph := '?';
    end;
    
    function std_bn(num, nam : pstr) : pstr;
    
    begin
       std_bn := num;
    end;
    
    function pgsql_ph(num, nam, typ : pstr) : pstr;
    
    begin
       pgsql_ph := '$' + num + '::' + typ;
    end;
    
    function pgsql_bn(num, nam : pstr) : pstr;
    
    begin
       pgsql_bn := num;
    end;
    
    function ora_ph(num, nam, typ : pstr) : pstr;
    
    begin
       ora_ph := ':' + nam;
    end;
    
    function ora_bn(num, nam : pstr) : pstr;
    
    begin
       ora_bn := nam;
    end;
    
    begin
       test('ODBC - MS SQLServer', 'arnepc5', 9001, 'arne', 'topsecret', std_ph, std_bn);
       test('ODBC - IBM DB2', 'arnepc5', 9002, 'arne', 'topsecret', std_ph, std_bn);
       test('local - Oracle DB', 'arnepc5', 9003, 'arne', 'topsecret', ora_ph, ora_bn);
       test('local - IBM DB2', 'arnepc5', 9004, 'arne', 'topsecret', std_ph, std_bn);
       test('local - MySQL', 'arnepc5', 9005, 'arne', 'topsecret', std_ph, std_bn);
       test('local - PostgreSQL', 'arnepc5', 9006, 'arne', 'topsecret', pgsql_ph, pgsql_bn);
    end.
    
    

    Article history:

    Version Date Description
    0.9 January 7th 2025 Pre-release
    1.0 June 9th 2026 Initial version
    1.1 June 12th 2026 Add RabbitMQ section

    Other articles:

    See list of all articles here

    Comments:

    Please send comments to Arne Vajhøj