/* dbio.p  
   Usage:  run dbio.p (INPUT cAction,
                       INPUT sessid,
                       INPUT-OUTPUT TABLE tt_user,
                       OUTPUT cMsg).
   
   ACTION= add_rec     Add new record
           update_rec  Update existing record
           get_rec     Get record & prepare for update
           find_rec    Get record with no update preparation
             
   Add/Update/Find the record identified by UserID in the 
   passed tt_user record.                                                   
*/

DEFINE TEMP-TABLE tt_user NO-UNDO LIKE user.
       
DEFINE TEMP-TABLE TT_OPTIMISTIC LIKE user.

DEFINE INPUT PARAMETER cACTION AS CHAR NO-UNDO.
DEFINE INPUT PARAMETER sessid AS CHAR NO-UNDO.
DEFINE INPUT-OUTPUT PARAMETER TABLE FOR tt_user.
DEFINE OUTPUT PARAMETER cMSG AS CHAR NO-UNDO.

DEFINE VARIABLE WKEQUAL         AS LOGICAL NO-UNDO.
DEFINE VARIABLE iCount          AS INTEGER NO-UNDO.
DEFINE VARIABLE iLockRetryCount AS INTEGER INITIAL 5 NO-UNDO.

FIND FIRST tt_user NO-LOCK NO-ERROR.
IF NOT AVAILABLE tt_user THEN DO:
   cMsg = "No temp-table passed.". 
   RETURN.
END.

CASE cACTION:
  WHEN "add_rec" THEN DO:  /* ADD */
     /* We don't allow specifying a UserID in an ADD.
        if UserID is non-zero, this is an error.  */
     IF tt_user.UserID NE 0 THEN DO:
        cMSG = "Not allowed to create a record with a specific UserID.".
        RETURN.
     END.

     DO TRANSACTION:
        CREATE user.
        
        ASSIGN user.UserID = NEXT-VALUE(UserID).
        
        BUFFER-COPY tt_user EXCEPT UserID TO user.
        
        /* Assign the temp-table UserID to return with buffer. */
        tt_user.UserID = user.UserID.
     END.

     RETURN.
  END /* WHEN add_rec */.

  WHEN "update_rec"  THEN DO TRANSACTION: /* UPDATE */
     /* Get Record to Update */
     DO iCount = 1 TO iLockRetryCount:
         /* Attempt to lock the record but don't wait, as there
            is no "user" to hit CTRL-C to break a dead-lock. */
         FIND user EXCLUSIVE-LOCK 
         WHERE user.UserID = tt_user.UserID NO-WAIT NO-ERROR. 
        IF AVAILABLE user THEN LEAVE.

        /* If the record is not available, the 3 possibilities:
           It's locked, ambiguous (not with a unique key) 
           or there is no record with this UserID. */
        IF NOT AVAIL user THEN DO:
           IF LOCKED user THEN DO:
              IF iCount > iLockRetryCount THEN DO:
                 cMSG = "Record is locked by another user or process.".
                 RETURN.
              END.
              /* Otherwise, wait one second and try again. */
              PAUSE 1 NO-MESSAGE.
              iCount = iCount + 1.
              NEXT.
           END.
           /* Otherwise, there is no record for this UserID. */
           cMSG = "Record #" + STRING(tt_user.UserID) + " Not Found".
           RETURN.
        END /* IF NOT AVAIL user */.
     END /* DO iCount EQ 1 TO iLockRetryCount */.

     /* To be here, we must have an exclusive-lock on our record. */
     /* Optimistic Locking Logic */
     FIND FIRST SESSREC 
          WHERE SESSREC.SESSID   = sessid
            AND SESSREC.FILENAME = "customer"
            AND SESSREC.RECROWID = STRING(ROWID(user))
             EXCLUSIVE-LOCK NO-ERROR.

     IF NOT AVAIL SESSREC THEN DO:
         cMSG = "No Optimistic Locking for " +
                 STRING(user.UserID).
        RETURN.
     END.  

     CREATE TT_OPTIMISTIC.
     RAW-TRANSFER FIELD SESSREC.RECDATA TO TT_OPTIMISTIC.

     BUFFER-COMPARE TT_OPTIMISTIC TO user 
        SAVE RESULT IN WKEQUAL.

     IF NOT WKEQUAL THEN DO:
        cMSG = "Record has changed!".                          
        RETURN.
     END.

     BUFFER-COPY tt_user TO user.    
     cMsg = "Record updated.".
   END.

   WHEN "get_rec" OR WHEN "find_rec" THEN DO:
      FIND user NO-LOCK 
      WHERE user.UserID = tt_user.UserID NO-ERROR.
          
      IF NOT AVAIL user THEN DO:
         cMSG = "Record #" + STRING(tt_user.UserID) + " Not Found".
         RETURN.
      END.

     IF cAction EQ "get_rec" THEN DO TRANSACTION:
         FIND FIRST SESSREC EXCLUSIVE-LOCK 
              WHERE SESSREC.SESSID   = sessid
                AND SESSREC.FILENAME = "customer"
                AND SESSREC.RECROWID = STRING(ROWID(user)) NO-ERROR.
                 
         IF NOT AVAIL SESSREC THEN DO:
            CREATE SESSREC.
            ASSIGN SESSREC.SESSID = sessid
                   SESSREC.FILENAME = "customer"
                   SESSREC.RECROWID = STRING(ROWID(user)).
         END.              

         RAW-TRANSFER user TO FIELD SESSREC.RECDATA.
      END. /* DO TRANSACTION */
                                
      DELETE tt_user.
      CREATE tt_user.                         
      BUFFER-COPY user TO tt_user.
   END.
END CASE.
