Saturday, January 13, 2018

Writing records to disk immediately using MIO

Last week, I ran into a scenario where I needed to create two subroutines that are called sequentially by another subroutine. The first sub creates a record and then the second sub queries the database for the record and performs extra processing. The workflow looks like this:
Overall Subroutine Begins
   Subroutine 1: creates a record
   Subroutine 2: does a select to obtain the record created in subroutine 1
Overall Subroutine Ends
There was a problem with this workflow that I was using just a FOR_THE command to create the record in sub 1. Since the record created in sub 1 stays in the running buffer, it never gets written back to disk until the overall sub ends. Because of that, sub 2 never finds the new record.

To get around this, my colleagues at Ferrilli suggested manually changing the MIO level so that sub 1's record is written to disk immediately. The code to be added is:

CALL @MIO.TRANSACTION(MIO.TX.SUSPEND)
FOR_THE record_id
  * set field values
END_THE record_id
CALL @MIO.TRANSACTION(MIO.TX.RESUME)

The two MIO calls modify the level of the buffer and that's where the magic happens. Using MIO.TX.SUSPEND, in effect, temporarily starts a new process level at level 0.  All I/O that is performed after the call to MIO.TRANSACTION using MIO.TX.SUSPEND will be held in MIO's buffers until the call to MIO.TRANSACTION using MIO.TX.RESUME is reached. In our case, as MIO.TX.RESUME is reached, the buffer of the transaction is flushed and the record is written to disk. Afterward, the MIO level returns to the normal running state. So my pseudo code looks like this at the end:

Overall Subroutine Begins
   Subroutine 1: creates a record
      CALL @MIO.TRANSACTION(MIO.TX.SUSPEND)
      FOR_THE record_id
          * set field values
      END_THE record_id
      CALL @MIO.TRANSACTION(MIO.TX.RESUME)
   Subroutine 2: does a select to obtain the record created in subroutine 1
Overall Subroutine Ends
Thanks Neal, Lea, and Geoff for the documentation, and Thomas Mantooth for verifying the code. GO FIGTEAM!

Wednesday, September 6, 2017

Record markers to ASCII (and vice versa)

Credits to Thomas Mantooth.

The UniData delimiters are in reverse order of their hierarchy. So, going in the other direction...

@RM (Record mark) = CHAR(255)
@FM (Field mark) = CHAR(254)
@VM (Value mark) = CHAR(253)
@SM (Sub-value mark) = CHAR(252)
@TM (Text mark) = CHAR(251)

One neat trick you can make use of with this comes into play when you use the REMOVE command. It assigns a value that indicates what type of delimiter it returns. Most of the time, you don't care what the specific delimiter is - you're just looking to see if it's zero, which is returned at the end when there's no delimiter. However, if you're dealing with something that can have multiple delimiters, you can use REMOVE and the delimiter value to build a new dynamic array with the same delimiters very simply.

LOOP
    REMOVE X.VALUE FROM XL.ARRAY SETTING X.DELIMITER
    * Do something to manipulate the data
    XL.NEW.ARRAY := X.VALUE
UNTIL NOT(X,DELIM) DO
    XL.NEW.ARRAY := CHAR(256 - X.DELIMITER)
REPEAT

Thursday, July 6, 2017

Check if a record exists

There are a few ways to check if a record exists in a file.

1. Call the S.VERIFY.RECORD.EXISTS subroutine:

X.RECORD.ID = record ID to check

CALL S.VERIFY.RECORD.EXISTS(X.EXISTS, X.FILE.NAME, X.RECORD.ID)

IF X.EXISTS = 0 THEN // record doesn't exists
END
2. Use  CONFIRMED command (this is good for when the subroutine can't be called, such as in IS sub):

X.FILE.NAME = A.FILE.NAME
X.RECORD.ID = A.RECORD.ID
A.EXISTS = 0
IF LEN(X.FILE.NAME) AND LEN(X.RECORD.ID) THEN
CONFIRM X.RECORD.ID IN_FILE(X.FILE.NAME)

IF CONFIRMED THEN
A.EXISTS = 1
END
END
Source: Neal Webb

Thursday, August 18, 2016

Padding preceding zeros to Colleague ID

In Colleague, ID numbers often have preceding 0s, and these are often truncated as the data is imported into Colleague. To add a small check to the ID to make sure it always is 7 number-long, you can add the following:

* Add zeros padding to PERSON ID
X.PERSON.ID.LEN = LEN(A.PERSON.ID)
X.ZEROS.TO.PAD = 7 - X.PERSON.ID.LEN
IF (X.ZEROS.TO.PAD GT 0) THEN
   A.PERSON.ID = STR("0", X.ZEROS.TO.PAD) : A.PERSON.ID
END

Monday, June 20, 2016

Detail to screens cross applications

If you are familiar with the application tree in Colleague, you know we can detail (UI screen) from a process to another within the same environment, or up one level, but not to the adjunct application. The tree looks like this:

                        UT
                          |
                     CORE
                   /      |      \
               ST     HR     CA

A screen from ST can detail to another screen in ST, or CORE, or UT, but it can't go to HR or CA. There's a trick to make this happen. If you want to detail to a HR screen from ST, you can do this:

   CALL S.SESSION.INIT('HR')
   CALL_SCREEN SUSPEND HRS105($PRIMARY)
   CALL S.SESSION.INIT('ST')

This code would initialize all environment variables required from HR application, open the screen, then reinitialize the ST application. 

Credit:  Eric Small from eCommunities.

Monday, May 30, 2016

WebAPI connection exception Error Subset Found: Server error-00150-No response returned from the server

When writing a Colleague custom WebAPI, I encountered a connection error while executing a transaction:

[Ellucian.Data.Colleague.Exceptions.ColleagueTransactionException] = {"Error Subset Found: Server error-00150-No response returned from the server."}

I made sure my credential was correct and the connection was established before calling the transaction. After much debugging, I found out that the string that was passed to the transaction exceeded the size limit of a field in Colleague. I passed in a 15 character long string and tried to write it to a 10 character long field in Colleague through the transaction, and it resulted in a truncation error which ended the Colleague session. This also returned the error above. They do not look like they're related, so it was hard to track down. Make sure you check the size of the arguments of the transaction when you get these error.

Friday, May 13, 2016

Check if a record is locked

It is useful to check to see if the record is lock before beginning any processing. This eliminates the process to spin and wait for the record to be unlocked. When you work withe the WebAPI, it is more important to return a status of "resource is unavailable please try again" than to wait and risk the process timing out. To check to see if a record is locked, follow the below template:

XKV.FILE = V.BPV.DOC.IDS
XR.FILE = ""
CALL @MIO.READ.RECORD(MIO.READU.EXIT.ON.LOCKED,XFV.FILE,XKV.FILE,XR.FILE)
IF MIO.STATUS = MIO.STAT.LOCKED THEN ;* file is locked
   X.ERROR = 1
   * Output your message, set the status, or do some other process
END ELSE ;* record is not lock, proceed like normal
   * normal record read/write
END

The following give you different statuses of the lock check:


To read a record from a previously opened file.
@MIO.READ.RECORD(read_option, FV.FILE, KV.FILE, R.FILE)

Where FV.FILE is the previously opened file, KV.FILE is the key of the
record to be read, and R.FILE is the array into which the record will be
read.

Read_option is one of the following. These are equates so they should not
be surrounded by quotes.

MIO.READ - Reads the record without locking

MIO.READU - Reads the record and locks. If the record is already locked by
someone else, the user will get a message and will have the opportunity to
cancel the command.

MIO.READU.NO.CANCEL - Same as READU but without the option to cancel if the
record is locked.

MIO.READ.VERIFY - Checks to see if the record exists. R.FILE will be set to
1 if the record exists and 0 if the record does not exist. The entire
record is not read into the R.FILE variable if this option is used.

MIO.READ.ORIG - Will read the record from disk instead of from the MIO
memory buffers.