Function Database_Services(@Service, @Params)
/***********************************************************************************************************************

    This program is proprietary and is not to be used by or disclosed to others, nor is it to be copied without written
    permission from SRP Computer Solutions, Inc.

    Name        :   Database_Services

    Description :   Handler program for all Database services.

    Notes       :   Application errors should be logged using the Error Services module. There are a few methodological
                    assumptions built into way errors are managed which are important to understand in order to properly
                    work with Error Services:

    Parameters  :
        Service         [in] -- Name of the service being requested
        Param1-10   [in/out] -- Additional request parameter holders
        Response       [out] -- Response to be sent back to the Controller (MCP) or requesting procedure

    History     :   (Date, Initials, Notes)
        03/27/17    dmb     Original programmer.
        05/02/17    dmb     Add error if WriteDatabaseRow is unable to lock row.
        10/31/17    dmb     Add CalculateColumn and GetTableCommuter services.
        11/06/17    fjt     [SRPFW-195] Added to FW v16.0.12
        05/25/18    dmb     Add GetUserLocks service.
        05/29/18    dmb     Add IsKeyIDLocked, IsKeyIDSelfLocked, and UnlockKeyID services.
        10/09/18    djs     Added ActivateRecord service, which sets @ID, @Record, and @DICT to enable {} shorthand.
        01/22/19    fjt     [SRPFW-195] Updated to include codes in error reporting
        02/28/19    dmb     [SRPFW-195] Add safety check in GetTableHandle to make sure the table's full 
                            handle is still in @TABLES before using the cached handle.
        03/12/19    dmb     [SRPFW-270] Add ClearTableHandle service so it is easy to remove the cached
                            table handle when situations such as aliasing a table from a different
                            volume occurs.
        03/12/19    dmb     [SRPFW-270] Add SetTableAlias service to alias a table and call the
                            ClearTableHandle service if thealias was successful.
        05/03/19    dmb     [SRPFW-273] Initialize TableName in the CalculateColumn service to prevent
                            potential VNAV errors.
        05/11/20    dmb     [SRPFW-313] Add GetTableNames service.
        06/29/20    dmb     [SRPFW-282] Update the GetTableNames service to support ExcludeDictionaries and
                            ExcludeIndexes arguments.
        08/04/20    djs     Commented out UnlockKeyID service. This service causes OpenInsight to crash. Use
                            ReleaseKeyIDLock instead.

***********************************************************************************************************************/

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP
$insert VOL_TABLE_EQUATES
$insert RTI_LH_INFO_EQUATES

Declare function    Memory_Services, Database_Services, SRP_Encode, RetStack, RTI_LH_Info, SRP_Path, SRP_Array
Declare subroutine  Memory_Services, Database_Services, Verify_LH, SRP_Stopwatch, Btree.Extract, Update_Index, Set_Status
Declare subroutine  RTI_LH_Info, Alias_Table, Push.Select, Pop.Select

GoToService else
    Error_Services('Add', Service : ' is not a valid service request within the ' : ServiceModule : ' module.')
end

Return Response OR ''


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Service Parameter Options
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Options BOOLEAN     = True$, False$


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Services
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//----------------------------------------------------------------------------------------------------------------------
// ActivateRecord
//
// TableName. The linear hash database table name. - [REQUIRED]
// KeyID. The KeyID to the database table. - [REQUIRED]
//
// Reads a data row for the indicated Key ID and database table. Sets @ID, @Record, and @DICT to enable {} shorthand.
//----------------------------------------------------------------------------------------------------------------------
Service ActivateRecord(TableName, KeyID, NotExpired, ExpirationDuration, IgnoreMFSRoutines)

    If TableName NE '' AND KeyID NE '' then
        @ID = KeyID
        DictTable = ''
        If TableName[1,5] _EQC 'DICT.' then
            DictTable = TableName
            TableName = TableName[-1, 'B.']             
        end else
            DictTable = 'DICT.':TableName
        end
        @DICT = Database_Services('GetTableHandle', DictTable)
        If Error_Services('NoError') then
            @Record = Database_Services('ReadDataRow',TableName,KeyID,NotExpired,ExpirationDuration,IgnoreMFSRoutines)
            If Not(Error_Services('NoError')) then
                Error_Services('Add','Error reading ':KeyID:' from the ':TableName:' table in the ':Service:' service.')
            end
        end else
            Error_Services('Add', 'Error retrieving handle for the ':TableName:' table in the ':Service:' service.')
        end
    end

    Response = @Record

end service


//----------------------------------------------------------------------------------------------------------------------
// CalculateColumn
//
// Called directly from within a calculation column. The name of the table and column is derived from the call stack
// and the associated table commuter, if it exists, is called with the appropriate arguments.
//----------------------------------------------------------------------------------------------------------------------
Service CalculateColumn()

    Response    = ''
    ColumnName  = ''
    TableName   = ''
    RetStack    = RetStack()
    For Each Item in RetStack using @FM
        If Index(Item, 'DICT.MFS', 1) then
            ColumnName  = Item[2, \00\, 1]
            CharPos     = BCol2() + 1
            TableHandle = Item[CharPos, GetByteSize(Item), 1]
            Locate TableHandle<1, 2> in @TABLES(TAB_HANDLE$) using @FM setting fPos then
                TableName       = @TABLES(0)<fPos>  ; // This technically returns the dictionary, but DICT. will be removed.
                TableName[1, 5] = ''
            end
        end
    Until (ColumnName NE '') OR (Item EQ '')
    Next Item

    If TableName NE '' then
        TableCommuter   = Database_Services('GetTableCommuter', TableName)
        If TableCommuter NE '' then
            Response    = Function(@TableCommuter('CalculateColumn', ColumnName))
        end
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// ClearTableHandle
//
// TableName. The linear hash database table name. - [REQUIRED]
//
// Clears the table handle array array from cache. This will force the GetTableHandle service to call the Open statement
// again.
//----------------------------------------------------------------------------------------------------------------------
Service ClearTableHandle(TableName)

    If TableName NE '' then
        ServiceKeyID    = ServiceModule : '*GetTableHandle'
        ServiceKeyID   := '*' : TableName
        Memory_Services('SetValue', ServiceKeyID, '')
    end else
        Error_Services('Add', 'TableName was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// DeleteDataRow
//
// TableName. The linear hash database table name. - [REQUIRED]
// KeyID. The KeyID to the database table. - [REQUIRED]
//
// Deletes a data row for the indicated Key ID and database table.
//----------------------------------------------------------------------------------------------------------------------
Service DeleteDataRow(TableName, KeyID, IgnoreSelfLock, IgnoreMFSRoutines)

    If TableName NE '' AND KeyID NE '' then
        If IgnoreSelfLock NE True$ then IgnoreSelfLock = False$
        If IgnoreMFSRoutines NE True$ then IgnoreMFSRoutines = False$
        HaveLock    = Database_Services('GetKeyIDLock', TableName, KeyID, IgnoreSelfLock)
        If HaveLock EQ True$ then
            TableHandle = Database_Services('GetTableHandle', TableName)
            If IgnoreMFSRoutines then
                MFSList = TableHandle<1, 1> ; // MFS routines are @SVM delimited.
                NumMFS = DCount(MFSList, @SVM)
                For MFSCnt = NumMFS to 1 Step -1
                    MFSRoutine  = MFSList<0, 0, MFSCnt>
                    If (MFSRoutine NE 'SI.MFS') AND (MFSRoutine NE 'RTP57') then
                        MFSList = Delete(MFSList, 0, 0, MFSCnt)
                    end
                Next MFSCnt
                TableHandle<1, 1>   = MFSList
            end
            
            // [SRPFW-195]  Updated / Added by GAC 18 May 2018
            If Error_Services('NoError') then
                Delete TableHandle, KeyID then
                    Memory_Services('SetValue', ServiceModule : '*' : 'ReadDatarow' : '*' : TableName : '*' : KeyID, '')
                end else
                    Error_Services('Add', 'Error deleting ' : KeyID : ' from the ' : TableName : ' table in the ' : Service : ' service. Error = ' : @File_Error<1>)
                end
            end
*            If Error_Services('NoError') then
                Database_Services('ReleaseKeyIDLock', TableName, KeyID)
*            end

        end else
            Error_Services('Add', 'Unable to lock ' : KeyID : ' for the ' : TableName : ' table in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetKeyIDLock
//
// Attempts to perform a semaphore lock on the indicated tablename and Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service GetKeyIDLock(TableName, KeyID, IgnoreSelfLock)

    HaveLock    = False$    ; // Assume false for now.

    If TableName NE '' AND KeyID NE '' then
        If IgnoreSelfLock NE True$ then IgnoreSelfLock = False$
        TableHandle = Database_Services('GetTableHandle', TableName)
        If Error_Services('NoError') then
            Lock Tablehandle, KeyID then
                HaveLock    = True$
            end else
                If IgnoreSelfLock EQ True$ then
                    Status  = Status()
                    If Status EQ 1 then
                        HaveLock    = True$
                    end
                end
            end
        end
    end else
        Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = HaveLock

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTableCommuter
//
// Returns the name of the indicated table's commuter module if it exists. If it does not exist then an empty string
// will be returned.
//----------------------------------------------------------------------------------------------------------------------
Service GetTableCommuter(TableName)

    ServiceKeyID   := '*' : TableName
    TableCommuter   = Memory_Services('GetValue', ServiceKeyID)

    If TableCommuter EQ '' then
        ObjExists       = False$    ; // Assume the object code for the action handler does not exist for now.
        For Each AppID in @AppId
            If AppID _EQC 'SYSPROG' then
                SysObjKey = '$' : TableName : '_ACTIONS'
            end else
                SysObjKey = '$' : TableName : '_ACTIONS' : '*' : AppID
            end
            ObjExists   = Memory_Services('KeyExists', SysObjKey)
        Until ObjExists
        Next AppID

        If Not(ObjExists) then
            For Each AppID in @AppId
                If AppID _EQC 'SYSPROG' then
                    SysObjKey = '$' : TableName : '_ACTIONS'
                end else
                    SysObjKey = '$' : TableName : '_ACTIONS' : '*' : AppID
                end
                OrigFileError   = @FILE.ERROR
                @FILE.ERROR     = ''
                BFS             = 'RTP57'
                // The handle to SYSOBJ is used to find object code before it gets called
                ActionStatus    = ''
                Call @BFS(2, BFS, @FILE_SYSOBJ<1, 2>, SysObjKey, FMC, SysObjRecord, ActionStatus)
                @FILE.ERROR     = OrigFileError
                If ActionStatus then ObjExists = True$
            Until ObjExists
            Next AppID
        end

        If (ObjExists) then
            TableCommuter   = TableName : '_ACTIONS'
            Memory_Services('SetValue', ServiceKeyID, TableCommuter)
        end
    end

    Response    = TableCommuter

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTableHandle
//
// TableName. The linear hash database table name. - [REQUIRED]
//
// Returns an array of information related to the database table being passed in.
//----------------------------------------------------------------------------------------------------------------------
Service GetTableHandle(TableName)

    TableHandle     = ''

    If TableName NE '' then
        ServiceKeyID   := '*' : TableName

        Locate TableName in @Tables(tab_name$) using @FM setting fPos then
            If @Tables(tab_handle$)<fPos> NE '' then
                // Only check for a cached handle if the table and its handle is still in @TABLES. Otherwise the handle is
                // probably stale and will hang OI when utilized.
                TableHandle     = Memory_Services('GetValue', ServiceKeyID)
            end
        end

        If TableHandle EQ '' then
            rv  = Set_Status(0)
            Open TableName to TableHandle then
                Memory_Services('SetValue', ServiceKeyID, TableHandle)
            end else
                Error_Services('Add', 'Unable to open the ' : TableName : ' table in the ' : Service : ' service. Error = ' : @File_Error<1>)
            end
        end
    end else
        Error_Services('Add', 'TableName was missing in the ' : Service : ' service.')
    end

    Response    = TableHandle

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTableNames
//
// ApplicationTablesOnly    - Boolean flag to determine if only application database tables should be returned (i.e.,
//                            exclude SYSTEM and non-RTP57 tables). Default is True.
// ExcludeDictionaries      - Boolean flag to determine if dictionary tables should be excluded. Default is False.
// ExcludeIndexes           - Boolean flag to determine if index tables should be excluded. Default is False.
//
// Returns an @FM list of currently attached OpenInsight database tables.
//----------------------------------------------------------------------------------------------------------------------
Service GetTableNames(ApplicationTablesOnly=BOOLEAN, ExcludeDictionaries=BOOLEAN, ExcludeIndexes=BOOLEAN)

    TableNames  = ''    ; // Initialize the return variable.

    If ApplicationTablesOnly    NE False$   then ApplicationTablesOnly  = True$
    If ExcludeDictionaries      NE True$    then ExcludeDictionaries    = False$
    If ExcludeIndexes           NE True$    then ExcludeIndexes         = False$

    TableNames  = @Tables(tab_name$)
    TableTypes  = @Tables(tab_vol_name$)
    If ApplicationTablesOnly EQ True$ then
        Tables  = ''
        For Each TableType in TableTypes using @FM setting fPos
            TableName   = TableNames<fPos>
            If (TableType[1, 'F*'] EQ 'RTP57') AND Not(TableType EQ 'RTP57*REVBOOT') AND Not(TableType EQ 'RTP57*O4WFILES') AND Not(TableType EQ 'RTP57*AREV_DIR') then
                Tables := TableName : @FM
            end
        Next TableType
        Tables[-1, 1]   = ''
        Transfer Tables to TableNames
    end
    If ExcludeDictionaries EQ True$ then
        Tables  = ''
        For Each TableName in TableNames using @FM
            If TableName[1, 5] NE 'DICT.' then
                Tables := TableName : @FM
            end
        Next TableType
        Tables[-1, 1]   = ''
        Transfer Tables to TableNames
    end
    If ExcludeIndexes EQ True$ then
        Tables  = ''
        For Each TableName in TableNames using @FM
            If TableName[1, 1] NE '!' then
                Tables := TableName : @FM
            end
        Next TableType
        Tables[-1, 1]   = ''
        Transfer Tables to TableNames
    end
    TableNames = SRP_Array('SortRows', TableNames, 'AL1', 'LIST', @FM, @VM)

    Response    = TableNames

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTableProperties
//
// TableName. The linear hash database table name. - [REQUIRED]
//
// Returns an array of information related to the database table being passed in.
//----------------------------------------------------------------------------------------------------------------------
Service GetTableProperties(TableName)

    TableProperties = ''

    If TableName NE '' then
        rv  = Set_Status(0)
        Locate TableName in @TABLES(tab_name$) using @FM setting fPos then
            TableProperties<1>  = @TABLES(tab_account$)<fPos>       ; // DatabaseID
            TableProperties<2>  = @TABLES(tab_file_sys$)<fPos>      ; // MFS/BFS List
            VolumeID            = @TABLES(tab_vol_name$)<fPos>      ; // Volume ID
            Locate VolumeID in @VOLUMES(vol_name$) using @FM setting fPos then
                TableProperties<3>  = @VOLUMES(vol_label$)<fPos>    ; // Volume Label
                TableProperties<4>  = @VOLUMES(vol_location$)<fPos> ; // Volume Path
                TableProperties<5>  = @VOLUMES(vol_file_sys$)<fPos> ; // BFS
            end
        end else
            Error_Services('Add', 'The ' : TableName : ' table is not attached and available to the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'TableName argument was missing in the ' : Service : ' service.')
    end

    Response    = TableProperties

end service


//----------------------------------------------------------------------------------------------------------------------
// GetUserLocks
//
// Returns a dynamic array of user lock information. Note, this can only be done with the UD 5. This can also cause
// instability with the current session and may require the Task Manager to close the session.
//----------------------------------------------------------------------------------------------------------------------
Service GetUserLocks()

    UserLocks   = RTI_LH_Info(CMD_LOCKS_INFO$, '')

    Response    = UserLocks

end service


//----------------------------------------------------------------------------------------------------------------------
// IsKeyIDLocked
//
// Returns a Boolean flag of the lock status for the indicated table and Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service IsKeyIDLocked(TableName, KeyID, IgnoreSelfLock=BOOLEAN)

    If IgnoreSelfLock NE True$ then IgnoreSelfLock = False$

    KeyIDLocked = False$    ; // Assume false for now.

    If (TableName NE '') AND (KeyID NE '') then
        Open TableName to hTable then
            Lock hTable, KeyID then
                // Able to lock Key ID. Unlock it right away.
                Unlock hTable, KeyID else Null
            end else
                // Unable to lock Key ID. Evaluate the type of lock.
                LockType    = Status()
                If LockType EQ 0 then
                    // Lock exists on another station.
                    KeyIDLocked = True$
                end else
                    // Lock exists on this station. Check to see if the Ignore Self Lock flag is set.
                    If Not(IgnoreSelfLock) then KeyIDLocked = True$
                end
            end
        end else
            Error_Services('Add', 'Error opening the ' : TableName : ' table in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = KeyIDLocked

end service


//----------------------------------------------------------------------------------------------------------------------
// IsKeyIDSelfLocked
//
// Returns a Boolean flag of the self-lock status for the indicated table and Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service IsKeyIDSelfLocked(TableName, KeyID)

    KeyIDSelfLocked = False$    ; // Assume false for now.

    If (TableName NE '') AND (KeyID NE '') then
        Open TableName to hTable then
            Lock hTable, KeyID then
                // Able to lock Key ID. Unlock it right away.
                Unlock hTable, KeyID else Null
            end else
                // Unable to lock Key ID. Evaluate the type of lock.
                LockType    = Status()
                If LockType EQ 1 then
                    // This Key ID is self-locked.
                    KeyIDSelfLocked = True$
                end
            end
        end else
            Error_Services('Add', 'Error opening the ' : TableName : ' table in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = KeyIDSelfLocked

end service


//----------------------------------------------------------------------------------------------------------------------
// ReadDataColumn
//
// TableName. The linear hash database table name. - [REQUIRED]
// KeyID. The KeyID to the database table. - [REQUIRED]
// ColumnNo. The index of the column that is to be read. - [REQUIRED]
//
// Reads a data column for the indicated Key ID and database table.
//----------------------------------------------------------------------------------------------------------------------
Service ReadDataColumn(TableName, KeyID, ColumnNo, NotExpired, ExpirationDuration, IgnoreMFSRoutines)
    
    If NotExpired NE False$ then NotExpired = True$
    If (ExpirationDuration EQ '') OR (Not(Num(ExpirationDuration))) then ExpirationDuration = 0
    If IgnoreMFSRoutines NE True$ then IgnoreMFSRoutines = False$

    ServiceKeyID   := '*' : TableName : '*' : KeyID : '*' : ColumnNo
    DataColumn      = Memory_Services('GetValue', ServiceKeyID, NotExpired, ExpirationDuration)

    If Num(ColumnNo) AND (ColumnNo GT 0) then
        If TableName NE '' AND KeyID NE '' then
            If DataColumn EQ '' then
                TableHandle = Database_Services('GetTableHandle', TableName)
                If Error_Services('NoError') then
                    If IgnoreMFSRoutines then
                        MFSList = TableHandle<1, 1> ; // MFS routines are @SVM delimited.
                        NumMFS = DCount(MFSList, @SVM)
                        For MFSCnt = NumMFS to 1 Step -1
                            MFSRoutine  = MFSList<0, 0, MFSCnt>
                            If MFSRoutine NE 'RTP57' then
                                MFSList = Delete(MFSList, 0, 0, MFSCnt)
                            end
                        Next MFSCnt
                        TableHandle<1, 1>   = MFSList
                    end
                    ReadV DataColumn from TableHandle, KeyID, ColumnNo then
                        Memory_Services('SetValue', ServiceKeyID, DataColumn)
                    end else
                        ErrorMsg = 'Error reading ' : KeyID : ' column number ' : ColumnNo : ' from the ' |
                                 : TableName : ' table in the ' : Service : ' service.'
                        Error_Services('Add', ErrorMsg)
                    end
                end
            end
        end else
            Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'ColumnNo was not a number or was not greater than zero in the ' : Service : ' service.')
    end

    Response    = DataColumn

end service


//----------------------------------------------------------------------------------------------------------------------
// ReadDataRow
//
// TableName - The linear hash database table name. - [REQUIRED]
// KeyID - The KeyID to the database table. - [REQUIRED]
//
// Reads a data row for the indicated Key ID and database table.
//----------------------------------------------------------------------------------------------------------------------
Service ReadDataRow(TableName, KeyID, NotExpired, ExpirationDuration, IgnoreMFSRoutines)

    If NotExpired NE False$ then NotExpired = True$
    If (ExpirationDuration EQ '') OR (Not(Num(ExpirationDuration))) then ExpirationDuration = 0
    If IgnoreMFSRoutines NE True$ then IgnoreMFSRoutines = False$

    ServiceKeyID   := '*' : TableName : '*' : KeyID
    DataRow         = Memory_Services('GetValue', ServiceKeyID, NotExpired, ExpirationDuration)

    If TableName NE '' AND KeyID NE '' then
        If DataRow EQ '' then
            TableHandle = Database_Services('GetTableHandle', TableName)
            If Error_Services('NoError') then
                If IgnoreMFSRoutines then
                    MFSList = TableHandle<1, 1> ; // MFS routines are @SVM delimited.
                    NumMFS = DCount(MFSList, @SVM)
                    For MFSCnt = NumMFS to 1 Step -1
                        MFSRoutine  = MFSList<0, 0, MFSCnt>
                        If MFSRoutine NE 'RTP57' then
                            MFSList = Delete(MFSList, 0, 0, MFSCnt)
                        end
                    Next MFSCnt
                    TableHandle<1, 1>   = MFSList
                end
                // [SRPFW-195]
                Read DataRow from TableHandle, KeyID then
                    Memory_Services('SetValue', ServiceKeyID, DataRow)
                end else
                    Error_Services('Add', 'Error reading ' : KeyID : ' from the ' : TableName : ' table in the ' : Service : ' service. Error = ' : @File_Error<1>)

                end
            end
        end
    end else
        Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = DataRow

end service


//----------------------------------------------------------------------------------------------------------------------
// ReleaseKeyIDLock
//
// Attempts to release a semaphore lock on the indicated tablename and Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service ReleaseKeyIDLock(TableName, KeyID)

    LockReleased    = False$    ; // Assume false for now.

    If TableName NE '' AND KeyID NE '' then
        TableHandle = Database_Services('GetTableHandle', TableName)
        If Error_Services('NoError') then
            UnLock Tablehandle, KeyID then
                LockReleased    = True$
            end else
                Error_Services('Add', 'Unable to unlock the ' : KeyID : ' Key ID from the ' : TableName : ' table in the ' : Service : ' service. Error = ' : @File_Error<1>)
            end
        end
    end else
        Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = LockReleased

end service


//----------------------------------------------------------------------------------------------------------------------
// SearchIndex
//
// TableName. The linear hash database table name. - [REQUIRED]
// ColumnName. The indexed column to search against. - [REQUIRED]
// SearchValue. The value being searched for. - [REQUIRED]
// UpdateIndex. Boolean flag to determine if the index for the indicated column should be updated before searching.
//              - [OPTIONAL]
//
// Returns an @FM delimited list of Key IDs that match the search value.
//----------------------------------------------------------------------------------------------------------------------
Service SearchIndex(TableName, ColumnName, SearchValue, UpdateIndex)

    If UpdateIndex NE True$ then UpdateIndex = False$

    ServiceKeyID   := '*' : TableName : '*' : ColumnName : '*' : SearchValue
    ServiceKeyID    = SRP_Encode(ServiceKeyID, 'BASE64')
    KeyIDList       = ''

    If TableName NE '' AND ColumnName NE '' AND SearchValue NE '' then
        If KeyIDList EQ '' then
            DictTableHandle = Database_Services('GetTableHandle', 'DICT.' : TableName)
            If Error_Services('NoError') then
                Set_Status(0)
                If UpdateIndex then Update_Index(TableName, ColumnName, False$, True$)
                Set_Status(0)
                Flag    = ''
                Btree.Extract(ColumnName : @VM : SearchValue : @FM, Tablename, DictTableHandle, KeyIDList, 'S', Flag)
                If Flag EQ 0 then
                    Convert @VM to @FM in KeyIDList
                    Memory_Services('SetValue', ServiceKeyID, KeyIDList)
                end else
                    Error_Services('Add', 'Error in Btree.Extract search from the ' : TableName : ' table in the ' : Service : ' service.')
                end
            end
        end
    end else
        Error_Services('Add', 'TableName, ColumnName, or SearchValue argument was missing in the ' : Service : ' service.')
    end

    Response    = KeyIDList

end service


//----------------------------------------------------------------------------------------------------------------------
// SetTableAlias
//
// TableName    - The linear hash database table name. - [Required]
// AliasName    - The alias name being set. - [Required]
// Volume       - The location path or volume name where the table exists. - [Required]
// DatabaseID   - Database ID of the table being aliased. Defaults to the current Database ID (@DBID) - [Optional]
//
// Attempts to create an alias for the indicated table, volume, and database. It returns a True$ if successful or a
// False$ if unsuccessful.
//----------------------------------------------------------------------------------------------------------------------
Service SetTableAlias(TableName, AliasName, Volume, DatabaseID)

    Success = False$    ; // Assume not successful for now.

    If (TableName NE '') AND (AliasName NE '') AND (Volume NE '') then
        Set_Status(0)
        If DatabaseID EQ '' then DatabaseID = @DBID
        Push.Select(F1, F2, F3, F4) ; // Protect cursors in case Alias_Table fails.
        Alias_Table(Volume : '', DatabaseID : '', TableName : '', AliasName : '')
        Pop.Select(F1, F2, F3, F4)  ; // Restore cursors.
        StatusCode  = ''
        If Get_Status(StatusCode) then
            Error_Services('Add', 'Error calling the Alias_Table routine in the ' : Service : ' service. Status Code : ' : StatusCode)
        end else
            // Setting an alias could changes the handle so clear the cached handle to avoid it being stale.
            Success = True$
            Database_Services('ClearTableHandle', AliasName)
        end
        Set_Status(0)
    end else
        Error_Services('Add', 'TableName, AliasName, or Volume argument was missing in the ' : Service : ' service.')
    end

    Response    = Success

end service


//----------------------------------------------------------------------------------------------------------------------
// UnlockKeyID
//
// Attempts to unlock the indicated Key ID from the indicated Table Name. Note, this can only be done with the UD 5.
//----------------------------------------------------------------------------------------------------------------------
* Service UnlockKeyID(TableName, KeyID)
* 
*    KeyIDUnlocked   = False$    ; // Assume false for now.
* 
*    Convert @Lower_Case to @Upper_Case in TableName
* 
*    If (TableName NE '') AND (KeyID NE '') then
*        // First, do a sanity check to confirm that there is a lock on this Key ID.
*        IsKeyIDLocked   = Database_Services('IsKeyIDLocked', TableName, KeyID, False$)
*        If Error_Services('NoError') AND (IsKeyIDLocked EQ False$) then
*            // Key ID was not locked.
*            KeyIDUnlocked   = True$
*        end
*        If KeyIDUnlocked NE True$ then
*            // Key ID is still presumed to be locked.
*            VolumePath  = ''
*            FileName    = ''
* 
*            // Resolve the volume path for this table.
*            hTableFull          = Database_Services('ReadDataRow', 'SYSTABLES', TableName)
*            VolumeName          = hTableFull<1>
*            VolumeRow           = Database_Services('ReadDataRow', 'SYSVOLUMES', VolumeName)
*            VolumePath          = VolumeRow[-1, 'B' : @FM]
*            VolumePath          = VolumePath[-1, 'B' : \0D\]
*            VolumePath[1, 12]   = ''    ; // Strip off the preceding bytes.
*            If SRP_Path('IsRelative', VolumePath) then
*                VolumePath  = SRP_Path('Combine', Drive(), VolumePath)
*            end
*            VolumePath  = SRP_Path('RemoveFilename', VolumePath)
*            // Make sure there is a backslash since the RTI_LH_INFO API seems to use this.
*            VolumePath  = SRP_Path('AddBackslash', VolumePath)
* 
*            // Resolve the filename for this table.
*            hTableFull  = Database_Services('ReadDataRow', 'SYSTABLES', TableName)
*            FileName    = hTableFull[-1, 'B' : @FM]
*            FileName    = FileName[-1, 'B' : \0D\]
*            FileName    = FileName[1, @VM]
*            If FileName _NEC 'SYSREPOS' then FileName[1, 12] = ''    ; // Strip off the preceding bytes.
*            FileName    = FileName[-1, 'B' : '\']
*            // Make sure the filename is well formed.
*            Begin Case
*                Case (FileName[-3, 3] _EQC '.LK') OR (FileName[-3, 3] _EQC '.OV')
*                    FileName[-3, 3] = ''
*                Case FileName _EQC 'SYSREPOS'
*                    FileName        = 'REVREPOS'
*            End Case
* 
*            If (VolumePath NE '') AND (FileName) NE '' then
*                // Attempt to unlocked the Key ID.
*                RTI_LH_Info(CMD_UNLOCK$, VolumePath, FileName, KeyID, FileName)
*                // Confirm that the Key ID was unlocked.
*                IsKeyIDLocked   = Database_Services('IsKeyIDLocked', TableName, KeyID, False$)
*                If Error_Services('NoError') AND (IsKeyIDLocked EQ False$) then
*                    // Key ID was not locked.
*                    KeyIDUnlocked   = True$
*                end else
*                    // Unable to unlock the Key ID which could be because it is locked by this station. Try using the
*                    // Unlock statement.
*                    hTable  = Database_Services('GetTableHandle', TableName)
*                    Unlock hTable, KeyID then
*                        KeyIDUnlocked   = True$
*                    end else
*                        Error_Services('Add', 'Unable to unlock ' : KeyID : ' from the ' : TableName : ' table in the ' : Service : ' service.')
*                    end
*                end
*            end else
*                Error_Services('Add', 'No valid VolumePath or FileName was available in the ' : Service : ' service.')
*            end
*        end
*    end else
*        Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
*    end
* 
*    Response    = KeyIDUnlocked
* 
* end service


//----------------------------------------------------------------------------------------------------------------------
// VerifyLH
//
// TableNames. One or more linear hash database tables to verify. List should be @FM delimited. - [OPTIONAL]
// SaveList. Name of a saved selection of linear hash database tables to verify. This argument will be ignored
//          if TableNames is populated. - [OPTIONAL]
//
// Performs a health check against the indicated tables and returns back any issues. Note: This uses the Verify_LH
// subroutine to check for GFEs. All results are stored in the SYSLHVERIFY table with a KeyID of
// VolumeLabel*DatabaseID*TableName. Returns the list of groups that have GFEs or returns an empty string if there are
// none. The list of GFEs or empty strings will themselves be @FM delimited to correspond with the tables passed into
// this service.
//----------------------------------------------------------------------------------------------------------------------
Service VerifyLH(Tablenames, SaveList)

    VerifyResults   = ''

    If TableNames EQ '' then
        TableNames  = Xlate('SYSLISTS', SaveList, '', 'X')
        If Index(TableNames<1>, @TM, 1) then
            // This saved select has metadata in attribute 1. Remove it.
            TableNames  = Delete(TableNames, 1, 0, 0)
        end
    end

    If TableNames NE '' then
        NumTables   = DCount(TableNames, @FM)
        For TableCnt = 1 to NumTables
            TableName       = TableNames<TableCnt>
            TableProperties = Database_Services('GetTableProperties', TableName)
            If Error_Services('NoError') then
                rv  = Set_Status(0)
                Verify_LH('', TableName, True$, False$, False$)
                Open 'SYSLHVERIFY' to hSysLHVerify then
                    DatabaseID      = TableProperties<1>
                    VolumeLabel     = TableProperties<3>
                    LHVerifyKeyID   = VolumeLabel : '*' : DatabaseID : '*' : TableName
                    Read LHVerifyRec from hSysLHVerify, LHVerifyKeyID then
                        VerifyResults<TableCnt> = LHVerifyRec<2>
                    end
                end else
                    Error_Services('Add', 'Error opening the SYSLHVERIFY table in the ' : Service : ' service.')
                end
                Begin Case
                    Case @File_Error NE ''
                        Error_Services('Add', 'Error verifying table ' : TableName : ': FS Error = ' : @File_Error<1>)
                    Case Get_Status()
                        Error_Services('Add', 'Get_Status error: ' : Get_Status())
                End Case
            end
        Next TableCnt
    end else
        Error_Services('Add', 'The TableName argument was missing in the ' : Service : ' service.')
    end

    Response    = VerifyResults

end service


//----------------------------------------------------------------------------------------------------------------------
// VerifyLHAll
//
// Performs a health check against all attached tables and returns back any issues. Returns two lists which are
// delimited by an @RM. The first list is an @FM list of attached tables. The second list is an @FM list of results
// (groups that have GFEs or an empty string if there are none). Items in each list correspond which each other based on
// their list position.
//----------------------------------------------------------------------------------------------------------------------
Service VerifyLHAll()

    VerifyResults   = ''
    TableNames      = @TABLES(0)
    NumTables       = DCount(TableNames, @FM)

    For TableCnt = 1 to NumTables
        TableName       = TableNames<TableCnt>
        VerifyResults  := Database_Services('VerifyLH', TableName) : @FM
    Next TableCnt
    VerifyResults[-1, 1]    = ''    ; // Strip off the last @FM

    Response    = TableNames : @RM : VerifyResults

end service


//----------------------------------------------------------------------------------------------------------------------
// WriteDataRow
//
// TableName. The linear hash database table name. - [REQUIRED]
// KeyID. The KeyID to the database table. - [REQUIRED]
//
// Writes a data row for the indicated Key ID and database table.
//----------------------------------------------------------------------------------------------------------------------
Service WriteDataRow(TableName, KeyID, DataRow, IgnoreSelfLock, IgnoreMFSRoutines, IgnoreAllLocks)

    If TableName NE '' AND KeyID NE '' then
        If IgnoreSelfLock NE True$ then IgnoreSelfLock = False$
        If IgnoreMFSRoutines NE True$ then IgnoreMFSRoutines = False$
        If IgnoreAllLocks NE True$ then IgnoreAllLocks = False$
        If IgnoreAllLocks then
            HaveLock    = True$
        end else
            HaveLock    = Database_Services('GetKeyIDLock', TableName, KeyID, IgnoreSelfLock)
        end
        If HaveLock EQ True$ then
            TableHandle = Database_Services('GetTableHandle', TableName)
            If IgnoreMFSRoutines then
                MFSList = TableHandle<1, 1> ; // MFS routines are @SVM delimited.
                NumMFS = DCount(MFSList, @SVM)
                For MFSCnt = NumMFS to 1 Step -1
                    MFSRoutine  = MFSList<0, 0, MFSCnt>
                    If (MFSRoutine NE 'SI.MFS') AND (MFSRoutine NE 'RTP57') then
                        MFSList = Delete(MFSList, 0, 0, MFSCnt)
                    end
                Next MFSCnt
                TableHandle<1, 1>   = MFSList
            end
            
            // [SRPFW-195] Updated / added by GAC 18 May 2018           
            If Error_Services('NoError') then
                Write DataRow to TableHandle, KeyID then
                    Memory_Services('SetValue', ServiceModule : '*' : 'ReadDatarow' : '*' : TableName : '*' : KeyID, DataRow)
                end else
                    ErrorMsg = Error_Services('GetMessage')
                    Error_Services('Add', 'Error writing ' : KeyID : ' to the ' : TableName : ' table in the ' : Service : ' service. Error message: ':ErrorMsg)
                end
            end
*            If Error_Services('NoError') then
                If IgnoreAllLocks EQ False$ then
                    Database_Services('ReleaseKeyIDLock', TableName, KeyID)
                end
*            end
        end else
            Error_Services('Add', 'Unable to lock ' : KeyID : ' for the ' : TableName : ' table in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// UnlockKeyID
//
// Attempts to unlock the indicated Key ID from the indicated Table Name. Note, this can only be done with the UD 5.
//----------------------------------------------------------------------------------------------------------------------
Service UnlockKeyID(TableName, KeyID)

    KeyIDUnlocked = False$    ; // Assume false for now.
    Convert @Lower_Case to @Upper_Case in TableName

    If (TableName NE '') AND (KeyID NE '') then
        // First, do a sanity check to confirm that there is a lock on this Key ID.
        IsKeyIDLocked   = Database_Services('IsKeyIDLocked', TableName, KeyID, False$)
        If Error_Services('NoError') AND (IsKeyIDLocked EQ False$) then
            // Key ID was not locked.
            KeyIDUnlocked   = True$
        end
        If KeyIDUnlocked NE True$ then
            // Key ID is still presumed to be locked.
            VolumePath  = ''
            FileName    = ''

            // Resolve the volume path for this table.
            hTableFull          = Database_Services('ReadDataRow', 'SYSTABLES', TableName)
            VolumeName          = hTableFull<1>
            VolumeRow           = Database_Services('ReadDataRow', 'SYSVOLUMES', VolumeName)
            VolumePath          = VolumeRow[-1, 'B' : @FM]
            VolumePath          = VolumePath[-1, 'B' : \0D\]
            VolumePath[1, 12]   = ''    ; // Strip off the preceding bytes.
            If SRP_Path('IsRelative', VolumePath) then
                VolumePath  = SRP_Path('Combine', Drive(), VolumePath)
            end
            VolumePath  = SRP_Path('RemoveFilename', VolumePath)
            // Make sure there is a backslash since the RTI_LH_INFO API seems to use this.
            VolumePath  = SRP_Path('AddBackslash', VolumePath)

            // Resolve the filename for this table.
            hTableFull  = Database_Services('ReadDataRow', 'SYSTABLES', TableName)
            FileName    = hTableFull[-1, 'B' : @FM]
            FileName    = FileName[-1, 'B' : \0D\]
            FileName    = FileName[1, @VM]
            If FileName _NEC 'SYSREPOS' then FileName[1, 12] = ''    ; // Strip off the preceding bytes.
            FileName    = FileName[-1, 'B' : '\']
            // Make sure the filename is well formed.
            Begin Case
                Case (FileName[-3, 3] _EQC '.LK') OR (FileName[-3, 3] _EQC '.OV')
                    FileName[-3, 3] = ''
                Case FileName _EQC 'SYSREPOS'
                    FileName        = 'REVREPOS'
            End Case

            If (VolumePath NE '') AND (FileName) NE '' then
                // Attempt to unlocked the Key ID.
                RTI_LH_Info(CMD_UNLOCK$, VolumePath, FileName, KeyID, FileName)
                // Confirm that the Key ID was unlocked.
                IsKeyIDLocked   = Database_Services('IsKeyIDLocked', TableName, KeyID, False$)
                If Error_Services('NoError') AND (IsKeyIDLocked EQ False$) then
                    // Key ID was not locked.
                    KeyIDUnlocked   = True$
                end else
                    // Unable to unlock the Key ID which could be because it is locked by this station. Try using the
                    // Unlock statement.
                    hTable  = Database_Services('GetTableHandle', TableName)
                    Unlock hTable, KeyID then
                        KeyIDUnlocked   = True$
                    end else
                        Error_Services('Add', 'Unable to unlock ' : KeyID : ' from the ' : TableName : ' table in the ' : Service : ' service.')
                    end
                end
            end else
                Error_Services('Add', 'No valid VolumePath or FileName was available in the ' : Service : ' service.')
            end
        end
    end else
        Error_Services('Add', 'TableName or KeyID argument was missing in the ' : Service : ' service.')
    end
    Response = KeyIDUnlocked

end service


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Internal GoSubs
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



