Function SQL_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        :   SQL_Services

    Description :   Handler program for all module related services.

    Notes       :   The generic parameters should contain all the necessary information to process the services. Often
                    this will be information like the data Record and Key ID.

    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

    Metadata    :

    History     :   (Date, Initials, Notes)
        08/13/18    dmb     Original programmer.

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

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP
$insert MICROSOFT_ADO_EQUATES
$insert DICT_EQUATES

Declare function    Memory_Services, SQL_Services, Database_Services, SRP_JSON, SRP_Array, SRP_Hash, SRP_COM, Logging_Services
Declare subroutine  Memory_Services, SQL_Services, Database_Services, SRP_JSON, SRP_Stopwatch, SRP_COM, Logging_Services,Sleepery

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$
Options ProviderTypes   = 'ODBC', 'SQLNCLI11'
Options Servers         = 'mesirwdb002.irworld.irf.com', '10.95.128.28\PROD1,53959', 'messa03ec.ec.local'


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

//----------------------------------------------------------------------------------------------------------------------
// WriteDataRowToSQL
//
// High level services that writes OpenInsight data into a designated SQL table. This services calls other lower
// level services as needed and assumes an OpenInsight to SQL map has already been created.
//----------------------------------------------------------------------------------------------------------------------
Service WriteDataRowToSQL(OITableName, OIKeyID, OIRow, OIColumns)

    If (OITableName NE '') AND (OIKeyID NE '') AND (OIRow NE '') then
        // Create a new or get an existing Connection Object.
        SQLServer       = SQL_Services('GetSQLServer', OITableName)
        objConnection   = SQL_Services('GetConnectionObject', SQLServer, 2)

        If Error_Services('NoError') then
            // Set up transaction processing, initiate SQL queries, and commit the transaction.
            TransactionQueries  = ''
*            TransactionQueries := "SET TRANSACTION NAME 'oi'" : @FM

            // Create the DELETE statements so the data in SQL is cleared out first.
*            TransactionQueries := SQL_Services('GetSQLDeleteStatements', OITableName, OIKeyID, OIRow) : @FM

            // Create the INSERT statements to store the data in SQL.
            TransactionQueries := SQL_Services('GetSQLInsertStatements', OITableName, OIKeyID, OIRow, OIColumns) : @FM

            // End transaction processing.
*            TransactionQueries := 'COMMIT' : @FM

            TransactionQueries[-1, 1] = ''
        end

        If Error_Services('NoError') then
            // Process the tranaction queries.
            SQL_Services('ExecuteQueries', objConnection, TransactionQueries)
        end
    end else
        Error_Services('Add', 'The OITableName, OIKeyID, or OIRow argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// DeleteDataRowFromSQL
//
// High level services that deletes data from a designated SQL table. This services calls other lower level services as
// needed and assumes an OpenInsight to SQL map has already been created.
//----------------------------------------------------------------------------------------------------------------------
Service DeleteDataRowFromSQL(OITableName, OIKeyID)

    If (OITableName NE '') AND (OIKeyID NE '') then
        // Create a new or get an existing Connection Object.
        SQLServer       = SQL_Services('GetSQLServer', OITableName)
        objConnection   = SQL_Services('GetConnectionObject', SQLServer, 2)

        If Error_Services('NoError') then
            // Set up transaction processing, initiate SQL queries, and commit the transaction.
            TransactionQueries  = ''
            TransactionQueries := "SET TRANSACTION NAME 'oi'" : @FM

            // Create the DELETE statements so the data in SQL is cleared out first.
            TransactionQueries := SQL_Services('GetSQLDeleteStatements', OITableName, OIKeyID) : @FM

            // End transaction processing.
            TransactionQueries := 'COMMIT' : @FM

            TransactionQueries[-1, 1] = ''
        end

        If Error_Services('NoError') then
            // Process the tranaction queries.
            SQL_Services('ExecuteQueries', objConnection, TransactionQueries)
        end
    end else
        Error_Services('Add', 'The OITableName or OIKeyID argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// ReadDataRowFromSQL
//
// High level services that reads data from a designated SQL table. This services calls other lower level services as
// needed and assumes an OpenInsight to SQL map has already been created.
//----------------------------------------------------------------------------------------------------------------------
Service ReadDataRowFromSQL(OITableName, OIKeyID)

    OIRow   = ''

    If (OITableName NE '') AND (OIKeyID NE '') then
        // Create a new or get an existing Connection Object.
        SQLServer       = SQL_Services('GetSQLServer', OITableName)
        objConnection   = SQL_Services('GetConnectionObject', SQLServer, 2)

        If Error_Services('NoError') then
            // Select the SQL data for the primary SQL table associated to the OpenInsight table.
            SQLTableName        = SQL_Services('GetPrimarySQLTable', OITableName)
            SQLSelectStatement  = SQL_Services('GetSQLSelectStatement', OITableName, SQLTableName, OIKeyID)
            SQLData             = SQL_Services('ExecuteQuery', objConnection, SQLSelectStatement, True$)
            If (SQLData NE '') AND (Error_Services('NoError')) then
                OIRow   = SQL_Services('GetMappedOIColumnValues', OITableName, SQLTableName, SQLData)
            end
        end

        If Error_Services('NoError') then
            // Select the SQL data for all secondary SQL tables associated to the OpenInsight table.
            SQLTableNames   = SQL_Services('GetSecondarySQLTables', OITableName)
            If SQLTableNames NE '' then
                For Each SQLTableName in SQLTableNames using @FM
                    SQLSelectStatement  = SQL_Services('GetSQLSelectStatement', OITableName, SQLTableName, OIKeyID)
                    // Create a new or get an existing Connection Object.
                    SQLServer       = SQL_Services('GetSQLServer', OITableName)
                    objConnection   = SQL_Services('GetConnectionObject', SQLServer, 2)

                    If Error_Services('NoError') then
                        SQLData = SQL_Services('ExecuteQuery', objConnection, SQLSelectStatement, True$)
                        If (SQLData NE '') AND (Error_Services('NoError')) then
                            OIRow   = SQL_Services('GetMappedOIColumnValues', OITableName, SQLTableName, SQLData, OIRow)
                        end
                    end
                While (Error_Services('NoError'))
                Next SQLTable
            end
        end
    end else
        Error_Services('Add', 'The OITableName or OIKeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = OIRow

end service


//----------------------------------------------------------------------------------------------------------------------
// GetRowCount
//
// Server           - Name of the server the connection object is bound to. This should be the same name that
//                    will be used in the Data Source connection string.
// TableName        - Database Table whose row count is being requested.
//
// High level services that counts the number of rows in a designated table. This services calls other lower level
// services.
//----------------------------------------------------------------------------------------------------------------------
Service GetRowCount(Server=Servers, TableName)

    RowCount    = ''

    If (Server NE '') AND (TableName NE '') then
        // Create a new or get an existing Connection Object.
        objConnection   = SQL_Services('GetConnectionObject', Server, 2)

        If Error_Services('NoError') then
            // Select the SQL data for the primary SQL table associated to the OpenInsight table.
            ErrorMessage        = ''
            SQLCountStatement   = 'SELECT COUNT(*) FROM ' : TableName
            RowCount            = SQL_Services('ExecuteQuery', objConnection, SQLCountStatement, True$)
            If Error_Services('HasError') then
                ErrorMessage    = Error_Services('GetMessage')
            end
            SQL_Services('DestroyConnectionObject', objConnection)
            If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)
        end
    end else
        Error_Services('Add', 'The Server or TableName argument was missing in the ' : Service : ' service.')
    end

    Response    = RowCount

end service


//----------------------------------------------------------------------------------------------------------------------
// GetDataRows
//
// Server           - Name of the server the connection object is bound to. This should be the same name that
//                    will be used in the Data Source connection string.
// QueryStatement   - Query command. This should be a properly syntaxed SQL statement.
//
// High level service that gets database rows from the indicated database using the indicated query statement. This
// service will automatically get the connection and query object needed to perform the query statement.
//----------------------------------------------------------------------------------------------------------------------
Service GetDataRows(Server=Servers, QueryStatement, AttemptsBeforeFailure)

    If AttemptsBeforeFailure EQ '' then AttemptsBeforeFailure = 2
    DataRows    = ''

    If (Server NE '') AND (QueryStatement NE '') then
        ErrorMessage    = ''
        objConnection   = SQL_Services('GetConnectionObject', Server, AttemptsBeforeFailure)
        If Error_Services('NoError') then
            If Error_Services('NoError') then
                DataRows    = SQL_Services('ExecuteQuery', objConnection, QueryStatement, True$)
            end
            // Get any errors from the GetConnectionObject service or the ExecuteQuery service.
            ErrorMessage    = Error_Services('GetMessage')
        end
        SQL_Services('DestroyConnectionObject', objConnection)
        If ErrorMessage then Error_Services('Set', ErrorMessage)
    end else
        Error_Services('Add', 'Server or QueryStatement argument was missing in the ' : Service : 'service.')
    end

    Response    = DataRows

end service


//----------------------------------------------------------------------------------------------------------------------
// GetConnectionObject
//
// Server                   - Name of the server the connection object is bound to. This should be the same name that
//                            will be used in the Data Source connection string.
// AttemptsBeforeFailure    - Number of attempts to make the connect before erroring out. Default is 1.
//
// Returns a Connection Object based on the server. A returned Connection Object means that the server has been
// successfully connected to with the provided credentials. This service uses caching, so until an existing Connection
// Object has been destroyed (see the DestroyConnectionObject service), it will continue to use the one already in
// existence.
//----------------------------------------------------------------------------------------------------------------------
Service GetConnectionObject(Server=Servers, AttemptsBeforeFailure)

    If AttemptsBeforeFailure EQ '' then AttemptsBeforeFailure  = 1

    objConnection   = ''

    // Check to see if the Connection Object already exists.
    ServiceKeyID   := '*' : Server
    objConnection   = Memory_Services('GetValue', ServiceKeyID)
    If objConnection EQ '' then
        If Server NE '' then
            Attempts    = 0
            Error       = ''
            Loop
                If SRP_COM(objConnection, 'CREATE', 'ADODB.Connection') then
                    Memory_Services('SetValue', objConnection : '*Server', Server)
                    If SRP_COM('', 'ERROR') then
                        Error           = SRP_COM('', 'ERROR')
                        SQL_Services('DestroyConnectionObject', objConnection)
                        objConnection   = ''
                    end else
                        ConnectionString    = SQL_Services('GetConnectionString', Server)
                        If Error_Services('NoError') then
                            OpenAttempts = 0
                            Loop
                                SRP_COM(objConnection, 'CALL', 'Open', ConnectionString)
                                HasConnectionError = SQL_Services('HasConnectionObjectErrors', objConnection)
                                If (HasConnectionError) and (OpenAttempts GE AttemptsBeforeFailure) then
                                    Error = SQL_Services('GetConnectionObjectErrors', objConnection)
                                    SQL_Services('DestroyConnectionObject', objConnection)
                                    objConnection = ''
                                end
                                OpenAttempts += 1
                            Until (objConnection NE '') OR (OpenAttempts GE AttemptsBeforeFailure)
                            Repeat
                        end
                    end
                end
                Attempts   += 1
            Until (objConnection NE '') OR (Attempts GE AttemptsBeforeFailure)
            Repeat
            If objConnection then
                Memory_Services('SetValue', ServiceKeyID, objConnection)
            end else
                Error_Services('Add', 'Unable to create the connection object to ' : Server : ' in the ' : Service : ' service: ' : Error)
            end
        end else
            Error_Services('Add', 'Server argument was missing from the ' : Service : ' service.')
        end
    end

    Response = objConnection

end service


//----------------------------------------------------------------------------------------------------------------------
// DestroyConnectionObject
//
// objConnection    - Connection Object (see GetConnectionObject service.)
//
// Destroys the Connection Object. This returns True$ if successful, False$ if not.
//----------------------------------------------------------------------------------------------------------------------
Service DestroyConnectionObject(objConnection)

    Destroyed   = False$    ; // Assume object is not destroyed for now.

    If objConnection NE '' then
        Error   = ''
        // Close the connection object, but don't worry about errors. It might have already been closed.
        SRP_COM(objConnection, 'CALL', 'Close')
        SRP_COM(objConnection, 'RELEASE')
        If SRP_COM('', 'ERROR') then
            Error       = SRP_COM('', 'ERROR')
        end else
            Destroyed   = True$
        end
        If Destroyed then
            SQL_Services('ClearConnectionErrors', objConnection)
            Server  = Memory_Services('GetValue', objConnection : '*Server')
            Memory_Services('SetValue', ServiceModule : '*GetConnectionObject*' : Server, '')
        end else
            SQL_Services('AddConnectionErrors', objConnection, Error)
            Error_Services('Add', 'Service : ' : Service : ', Error : ' : Error)            
        end
    end else
        Error_Services('Add', 'The objConnection argument was missing in the ' : Service : ' service.')
    end

    Response = Destroyed

end service


//----------------------------------------------------------------------------------------------------------------------
// HasConnectionObjectErrors
//
// objConnection    - Connection Object (see GetConnectionObject service.)
//
// Returns a Boolean flag indicating if the Connection Object has any connection errors.
//----------------------------------------------------------------------------------------------------------------------
Service HasConnectionObjectErrors(objConnection)

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

    If objConnection NE '' then
        objErrors   = SRP_COM(objConnection, 'GET', 'Errors')
        NumErrors   = SRP_COM(objErrors, 'GET', 'Count')
        If NumErrors GT 0 then HasConnectionObjectErrors = True$
        SRP_COM(objErrors, 'RELEASE')
    end else
        Error_Services('Add', 'The objConnection argument was missing in the ' : Service : ' service.')
    end

    Response    = HasConnectionObjectErrors

end service


//----------------------------------------------------------------------------------------------------------------------
// GetConnectionObjectErrors
//
// objConnection    - Connection Object (see GetConnectionObject service.)
//
// Returns a string of errors errors associated with the indicated Connection Object. Different error properties related
// to the same error object will be surrounded by square braces. Different error oejcts will be separated by a
// semi-colon.
//----------------------------------------------------------------------------------------------------------------------
Service GetConnectionObjectErrors(objConnection)

    ConnectionObjectErrors  = ''

    If objConnection NE '' then
        objErrors   = SRP_COM(objConnection, 'GET', 'Errors')
        NumErrors   = SRP_COM(objErrors, 'GET', 'Count')
        If NumErrors GT 0 then
            // Implementing a cap on the number of errors to retrieve. In some instances we are receiving over
            // one million errors, which takes far too long to process.
            If NumErrors GT 5 then NumErrors = 5
            For ErrorCnt = 1 to NumErrors
                objError                = SRP_COM(objErrors, 'GET', 'Item', ErrorCnt - 1)
                ErrorDescription        = SRP_COM(objError, 'GET', 'Description')
                If ErrorDescription NE 0 then
                    ConnectionObjectErrors := '[' : ErrorDescription : ']'
                    ConnectionObjectErrors := '[' : SRP_COM(objError, 'GET', 'NativeError') : ']'
                    ConnectionObjectErrors := '[' : SRP_COM(objError, 'GET', 'Number') : ']'
                    ConnectionObjectErrors := '[' : SRP_COM(objError, 'GET', 'Source') : ']'
                    ConnectionObjectErrors := '[' : SRP_COM(objError, 'GET', 'SQLState') : ']'
                    ConnectionObjectErrors := ';'
                end
                SRP_COM(objError, 'RELEASE')
            Next ErrorCnt
        end
        SRP_COM(objErrors, 'RELEASE')
        ConnectionObjectErrors[-1, 1] = ''
    end else
        Error_Services('Add', 'The objConnection argument was missing in the ' : Service : ' service.')
    end

    Response    = ConnectionObjectErrors

end service


//----------------------------------------------------------------------------------------------------------------------
// AddConnectionErrors
//
// objConnection    - Connection Object (see GetConnectionObject service.)
// Errors           - @FM delimited list of errors.
//
// Adds a list of errors to the indicated Connection Object so the GetConnectionErrors service can retrieve them at
// will. This list will accumulate until the ClearConnectionErrors service is called.
//----------------------------------------------------------------------------------------------------------------------
Service AddConnectionErrors(objConnection, Errors)

    objConnection       = objConnection[1, '*']
    ServiceKeyID        = ServiceModule : '*' : 'GetConnectionErrors' : '*' : objConnection
    ConnectionErrors    = Memory_Services('GetValue', ServiceKeyID)

    If (objConnection NE '') AND (Errors NE '') then
        If ConnectionErrors NE '' then
            ConnectionErrors    := @FM : Errors
        end else
            ConnectionErrors     = Errors
        end
        Memory_Services('SetValue', ServiceKeyID, ConnectionErrors)
    end else
        Error_Services('Add', 'The objConnection or Errors argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetConnectionErrors
//
// objConnection    - Connection Object (see GetConnectionObject service.)
//
// Returns an @FM list of all connection errors associated with the indicated Connection Object.
//----------------------------------------------------------------------------------------------------------------------
Service GetConnectionErrors(objConnection)

    objConnection       = objConnection[1, '*']
    ConnectionErrors    = ''

    If objConnection NE '' then
        ServiceKeyID        = ServiceModule : '*' : 'GetConnectionErrors' : '*' : objConnection
        ConnectionErrors    = Memory_Services('GetValue', ServiceKeyID)
    end else
        Error_Services('Add', 'The objConnection argument was missing in the ' : Service : ' service.')
    end

    Response    = ConnectionErrors

end service


//----------------------------------------------------------------------------------------------------------------------
// ClearConnectionErrors
//
// objConnection    - Connection Object (see GetConnectionObject service.)
//
// Clears all connection errors associated with the indicated Connection Object.
//----------------------------------------------------------------------------------------------------------------------
Service ClearConnectionErrors(objConnection)

    objConnection   = objConnection[1, '*']

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

end service


//----------------------------------------------------------------------------------------------------------------------
// HasConnectionErrors
//
// objConnection    - Connection Object (see GetConnectionObject service.)
//
// Returns a Boolean flag indicating of the Connection Object has any connection errors.
//----------------------------------------------------------------------------------------------------------------------
Service HasConnectionErrors(objConnection)

    objConnection   = objConnection[1, '*']
    HasErrors       = False$    ; // Assume no errors for now.

    If objConnection NE '' then
        ConnectionErrors = SQL_Services('GetConnectionErrors', objConnection)
        If ConnectionErrors NE '' then
            HasErrors   = True$
        end else
            HasErrors   = False$
        end
    end else
        Error_Services('Add', 'The objConnection argument was missing in the ' : Service : ' service.')
    end

    Response = HasErrors

end service


//----------------------------------------------------------------------------------------------------------------------
// GetConnectionString
//
// Returns a well formed Connection String for the current server. This is used for ADO connections.
//----------------------------------------------------------------------------------------------------------------------
Service GetConnectionString(Server)

    ServiceKeyID       := '*' : Server
    ConnectionString    = Memory_Services('GetValue', ServiceKeyID)
    If ConnectionString EQ '' then
        If (Server NE '') then
            ConnectionInfo  = Database_Services('ReadDataRow', 'APP_INFO', 'SQL_SERVER*' : Server)
            If ConnectionInfo NE '' then
                ConnectionString    = ConnectionInfo
                Convert @FM to ';' in ConnectionString
                ConnectionString   := ';'
                Memory_Services('SetValue', ServiceKeyID, ConnectionString)
            end else
                Error_Services('Add', Server : ' is not a valid server. [' : Service : ']')
            end
        end else
            Error_Services('Add', 'The Server argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = ConnectionString

end service


//----------------------------------------------------------------------------------------------------------------------
// ExecuteQuery
//
// objConnection    - Connection Object (see GetConnectionObject service.)
// QueryStatement   - Query command. This should be a properly syntaxed SQL statement.
// ReturnDataRows   - Boolean flag to determine if all data rows resulting from the query should automatically be
//                    returned. This is the same as if the caller used the GetAllQueryDataRows service, but this allows
//                    everything to be done in one service rather than two. Default is False$
//
// Executes the query against the Connection Object. If the ReturnDataRows argument is False$, then this returns True$
// if there are results, False$ if there are no results or if there is a problem running the services. Otherwise, the
// datarows from the query will be returned unless there is an error.
//----------------------------------------------------------------------------------------------------------------------
Service ExecuteQuery(objConnection, QueryStatement, ReturnDataRows)

    Results = False$    ; // Assume no results from this query for now.
    If ReturnDataRows NE True$ then ReturnDataRows = False$
    If (objConnection NE '') AND (QueryStatement NE '') then
        objRecordSet    = SRP_COM(objConnection, 'CALL', 'Execute', QueryStatement)
        If SRP_COM('', 'HASERROR') then
*        If SQL_Services('HasConnectionObjectErrors', objConnection) then
            Error       = SQL_Services('GetConnectionObjectErrors', objConnection)
            Error2      = SRP_COM('', 'ERROR')
            Error_Services('Add', 'ADO error in the ' : Service : ' service: ' : Error)
            Results     = ''    ; // Since there is an error, no result should be returned.
        end else
            If ReturnDataRows then
               Results = SQL_Services('GetAllQueryDataRows', objRecordSet, objConnection)
                If Error_Services('HasError') then
                    ErrorMessage    = Error_Services('GetMessage')                    
                End
            end else
                Results = True$
            end
        end
        SRP_COM(objRecordSet, 'CALL', 'Cancel')
        SRP_COM(objRecordSet, 'RELEASE')
    end else
        Error_Services('Add', 'The objConnection or QueryStatement argument was missing in the ' : Service : ' service.')
    end

    Response = Results

end service


//----------------------------------------------------------------------------------------------------------------------
// ExecuteQueries
//
// objConnection    - Connection Object (see GetConnectionObject service.)
// QueryStatements  - @FM delimited list of Query commands. These should be a properly syntaxed SQL statement.
//
// Executes one or more queries against the Connection Object. This returns True$ if all queries processed without any
// errors, False$ if there is a problem running any of the queries.
//----------------------------------------------------------------------------------------------------------------------
Service ExecuteQueries(objConnection, QueryStatements)

    AllAccepted = True$    ; // Assume all queries were accepted for now unless otherwise proven.

    If (objConnection NE '') AND (QueryStatements NE '') then
        For Each QueryStatement in QueryStatements using @FM
            objRecordSet    = SRP_COM(objConnection, 'CALL', 'Execute', QueryStatement)
            If SQL_Services('HasConnectionObjectErrors', objConnection) then
                Error       = SQL_Services('GetConnectionObjectErrors', objConnection)
                Error_Services('Add', 'ADO error in the ' : Service : ' service: ' : Error)
                AllAccepted = False$
            end
            SRP_COM(objRecordSet, 'CALL', 'Cancel')
            SRP_COM(objRecordSet, 'RELEASE')
        Until Not(AllAccepted)
        Next QueryStatement
    end else
        AllAccepted = False$
        Error_Services('Add', 'The objConnection or QueryStatement argument was missing in the ' : Service : ' service.')
    end

    Response = AllAccepted

end service


//----------------------------------------------------------------------------------------------------------------------
// GetAllQueryDataRows
//
// objRecordSet		- RecordSet Object (see ExecuteQuery service.)
// objConnection    - Connection Object (see GetConnectionObject service.)
//
// Gets all of the data rows from the RecordSet Object. This returns an @RM array of data rows.
//----------------------------------------------------------------------------------------------------------------------
Service GetAllQueryDataRows(objRecordSet, objConnection)

    DataRows    = ''
    TableName   = ''
    Error       = ''
    If objRecordSet NE '' then
        DataRows        = SRP_COM(objRecordSet, 'CALL', 'GetString', 2, -1, @FM, @RM)
        If SQL_Services('HasConnectionObjectErrors', objConnection) then
            Error       = SQL_Services('GetConnectionObjectErrors', objConnection)
		end
*        If SRP_COM('', 'ERROR') then
*            Error   = SRP_COM('', 'ERROR')
*        end
        If DataRows[-1, 1] EQ @RM then DataRows[-1, 1] = ''
        If DataRows EQ objRecordSet then
            DataRows    = ''            
            Error_Services('Add', 'Unknown ADO error in the ' : Service : ' service.')
        end else
            If SRP_COM('', 'ERROR') then
                Error   = SRP_COM('', 'ERROR')
                If IndexC(Error, 'current record has been deleted', 1) then
                    DataRows    = ''
                end else
                    Error_Services('Add', 'ADO error in the ' : Service : ' service: ' : SRP_COM('', 'ERROR'))
                end
            end
        end
    end else
        Error_Services('Add', 'The objRecordSet argument was missing in the ' : Service : ' service.')
    end

    Response = DataRows

end service


//----------------------------------------------------------------------------------------------------------------------
// GetServers
//
// Returns an @FM list of database servers.
//----------------------------------------------------------------------------------------------------------------------
Service GetServers()

    Servers = Memory_Services('GetValue', ServiceKeyID)

    If Servers EQ '' then
        hSysEnv = Database_Services('GetTableHandle', 'SYSENV')
        If Error_Services('NoError') then
            Select hSysEnv
            EOF     = False$
            Loop
                Readnext SysEnvKeyID else EOF = True$
            Until EOF
                If SysEnvKeyID[1, 31] EQ 'SRP_DATA_EXPORT*CONNECTION_INFO' then Servers := SysEnvKeyID[-1, 'B*'] : @FM
            Repeat
            Servers[-1, 1]  = ''
            Servers = SRP_Array('SortRows', Servers, 'AL1', 'LIST', @FM, @VM)
            Memory_Services('SetValue', ServiceKeyID, Servers)
        end
    end

    Response = Servers

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTableNames
//
// Server   - Name of the server the connection object is bound to. This should be the same name that
//            will be used in the Data Source connection string.
//
// Returns an @FM list of database table names available through the current Connection Object.
//----------------------------------------------------------------------------------------------------------------------
Service GetTableNames(Server=Servers)

    TableNames      = ''

    If Server NE '' then
        objConnection   = SQL_Services('GetConnectionObject', Server, 2)
        If Error_Services('NoError') then
            ErrorMessage    = ''
            QueryStatement  = "SELECT table_name FROM information_schema.tables WHERE TABLE_TYPE='BASE TABLE' AND TABLE_SCHEMA = 'dbo'"
            TableNames      = SQL_Services('ExecuteQuery', objConnection, QueryStatement, True$)
            If Error_Services('NoError') then
                Convert @RM to @FM in TableNames
            end else
                ErrorMessage    = Error_Services('GetMessage')
            end
            SQL_Services('DestroyConnectionObject', objConnection)
            If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)
        end
    end else
        Error_Services('Add', 'Server argument was missing from the ' : Service : ' service.')
    end

    Response = TableNames

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTableColumns
//
// Server       - Name of the server the connection object is bound to. This should be the same name that
//                will be used in the Data Source connection string.
// TableName    - Database Table whose column data is being requested.
//
// Returns an @FM/@VM array of database column information.
//----------------------------------------------------------------------------------------------------------------------
Service GetTableColumns(Server=Servers, TableName, DoNotDestroy)

    TableColumns    = ''
    If Unassigned(DoNotDestroy) Then DoNotDestroy = False$
    If (Server NE '') AND (TableName NE '') then
        objConnection   = SQL_Services('GetConnectionObject', Server, 2)
        If Error_Services('NoError') then
            ErrorMessage    = ''
*            QueryStatement  = 'SELECT column_name,data_type,character_maximum_length FROM information_schema.columns with (nolock) WHERE table_name = ' : "'" : TableName : "'"
            QueryStatement  = "SELECT '['+column_name+']' AS column_name,data_type,character_maximum_length FROM information_schema.columns with (nolock) WHERE table_name = " : "'" : TableName : "'"
            TableColumns    = SQL_Services('ExecuteQuery', objConnection, QueryStatement, True$)
            If Error_Services('NoError') then
                Convert @FM to @VM in TableColumns
                Convert @RM to @FM in TableColumns
            end else
                ErrorMessage    = Error_Services('GetMessage')
            end
            If DoNotDestroy = True$ Else
                SQL_Services('DestroyConnectionObject', objConnection)
            End
            If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)
        end
    end else
        Error_Services('Add', 'The Server or TableName argument was missing in the ' : Service : ' service.')
    end

    Response = TableColumns

end service


//----------------------------------------------------------------------------------------------------------------------
// SetOITableToSQLMap
//
// TableName    - OpenInsight table being mapped. - [Required]
// SQLMap       - JSON object containing the OI Table to SQL Map information. - [Required]
//
// Updates the stored JSON object that defines the mapping between the indicated OpenInsight table and the SQL table.
//----------------------------------------------------------------------------------------------------------------------
Service SetOITableToSQLMap(TableName, SQLMap)

    Convert @Lower_Case to @Upper_Case in TableName
    ServiceKeyID    = ServiceModule : '*' : 'GetOITableToSQLMap' : '*' : TableName

    If (TableName NE '') AND (SQLMap NE '') then
        Convert \0D0A\ to @FM in SQLMap ; // Makes it easier to inspect. The GetOITableToSQLMap service removes the @FMs.
        Database_Services('WriteDataRow', 'DICT.' : TableName, '%SQLMap%', SQLMap, True$)
        If Error_Services('NoError') then
            Convert @FM to '' in SQLMap
            Memory_Services('SetValue', ServiceKeyID, SQLMap)
        end
    end else
        Error_Services('Add', 'The TableName or SQLMap argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetOITableToSQLMap
//
// TableName    - OpenInsight table being mapped. - [Required]
//
// Returns JSON object that defines the mapping between the indicated OpenInsight table and the SQL table.
//----------------------------------------------------------------------------------------------------------------------
Service GetOITableToSQLMap(TableName)

    Convert @Lower_Case to @Upper_Case in TableName

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

    If OITableToSQLMap EQ '' then
        If TableName NE '' then
            OITableToSQLMap = Database_Services('ReadDataRow', 'DICT.' : TableName, '%SQLMap%')
            Convert @FM to '' in OITableToSQLMap
            If OITableToSQLMap NE '' then
                Memory_Services('SetValue', ServiceKeyID, OITableToSQLMap)
            end
        end else
            Error_Services('Add', 'The TableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response = OITableToSQLMap

end service


//----------------------------------------------------------------------------------------------------------------------
// SetOIColumnToSQLMap
//
// TableName    - OpenInsight table being mapped. - [Required]
// ColumnName   - Database column from the OpenInsight table. - [Required]
// SQLMap       - JSON object containing the OI Column to SQL Map information. - [Required]
//
// Updates the stored JSON object that defines the mapping between the indicated OpenInsight column and the SQL table.
//----------------------------------------------------------------------------------------------------------------------
Service SetOIColumnToSQLMap(TableName, ColumnName, SQLMap)

    Convert @Lower_Case to @Upper_Case in TableName
    Convert @Lower_Case to @Upper_Case in ColumnName
    ServiceKeyID    = ServiceModule : '*' : 'GetOIColumnToSQLMap' : '*' : TableName : '*' : ColumnName

    If (TableName NE '') AND (ColumnName NE '') AND (SQLMap NE '') then
        Convert @FM to '' in SQLMap
        Convert \0D0A\ to '' in SQLMap
        ColumnData              = Database_Services('ReadDataRow', 'DICT.' : TableName, ColumnName)
        ColumnData<DICT_USER1$> = SQLMap
        Database_Services('WriteDataRow', 'DICT.' : TableName, ColumnName, ColumnData, True$)
        If Error_Services('NoError') then
            Memory_Services('SetValue', ServiceKeyID, SQLMap)
        end
    end else
        Error_Services('Add', 'The TableName, ColumnName, or SQLMap argument was missing in the ' : Service : ' service.')
    end

end service


//----------------------------------------------------------------------------------------------------------------------
// GetAllOIColumnToSQLMaps
//
// TableName    - OpenInsight table being mapped. - [Required]
//
// Returns an @FM/@VM delimited array of all JSON objects that define the mapping being the table columns and their
// respective SQL table columns. Columns with no maps will return an empty string for the JSON object.
//----------------------------------------------------------------------------------------------------------------------
Service GetAllOIColumnToSQLMaps(TableName)

    Convert @Lower_Case to @Upper_Case in TableName
    ColumnSQLMaps   = ''

    If TableName NE '' then
        FieldArray      = Database_Services('ReadDataRow', 'DICT.' : TableName, '%FIELDS%')
        ColumnNames     = FieldArray<FIELDS_NAME$>
        For Each ColumnName in ColumnNames using @VM
            SQLMap      = SQL_Services('GetOIColumnToSQLMap', TableName, ColumnName)
            ColumnSQLMaps  := ColumnName : @VM
            If SQLMap NE '' then
                ColumnSQLMaps  := SQLMap
            end
            ColumnSQLMaps  := @FM
        Next ColumnName
        ColumnSQLMaps[-1, 1]    = ''
    end else
        Error_Services('Add', 'The TableName argument was missing in the ' : Service : ' service.')
    end

    Response = ColumnSQLMaps

end service


//----------------------------------------------------------------------------------------------------------------------
// GetOIColumnToSQLMap
//
// TableName    - OpenInsight table being mapped. - [Required]
// ColumnName   - Database column from the OpenInsight table. - [Required]
//
// Returns JSON object that defines the mapping between the indicated OpenInsight table column and the SQL table column.
//----------------------------------------------------------------------------------------------------------------------
Service GetOIColumnToSQLMap(TableName, ColumnName)

    Convert @Lower_Case to @Upper_Case in TableName
    Convert @Lower_Case to @Upper_Case in ColumnName
    ServiceKeyID       := '*' : TableName : '*' : ColumnName
    OIColumnToSQLMap    = Memory_Services('GetValue', ServiceKeyID)

    If OIColumnToSQLMap EQ '' then
        If (TableName NE '') AND (ColumnName NE '') then
            ColumnData          = Database_Services('ReadDataRow', 'DICT.' : TableName, ColumnName)
            OIColumnToSQLMap    = ColumnData<DICT_USER1$>
            If OIColumnToSQLMap NE '' then
                Memory_Services('SetValue', ServiceKeyID, OIColumnToSQLMap)
            end
        end else
            Error_Services('Add', 'The TableName or ColumnName argument was missing in the ' : Service : ' service.')
        end
    end

    Response = OIColumnToSQLMap

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSQLServer
//
// Returns the name of the SQL Server that the indicated OpenInsight table is mapped to.
//----------------------------------------------------------------------------------------------------------------------
Service GetSQLServer(OITableName)

    Convert @Lower_Case to @Upper_Case in OITableName
    ServiceKeyID   := '*' : OITableName
    SQLServer       = Memory_Services('GetValue', ServiceKeyID)

    If SQLServer EQ '' then
        If OITableName NE '' then
            OITableToSQLMap = SQL_Services('GetOITableToSQLMap', OITableName)
            If SRP_JSON(MapObj, 'PARSE', OITableToSQLMap) EQ '' then
                SQLServer   = SRP_JSON(MapObj, 'GETVALUE', 'SQLServer.Name')
                SRP_JSON(MapObj, 'RELEASE')
            end
            If SQLServer NE '' then Memory_Services('SetValue', ServiceKeyID, SQLServer)
        end else
            Error_Services('Add', 'The OITableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = SQLServer

end service


//----------------------------------------------------------------------------------------------------------------------
// GetPrimarySQLTable
//
// Returns the name of the primary SQL table that the indicated OpenInsight table is mapped to.
//----------------------------------------------------------------------------------------------------------------------
Service GetPrimarySQLTable(OITableName)

    Convert @Lower_Case to @Upper_Case in OITableName
    ServiceKeyID   := '*' : OITableName
    PrimarySQLTable = Memory_Services('GetValue', ServiceKeyID)

    If PrimarySQLTable EQ '' then
        If OITableName NE '' then
            OITableToSQLMap = SQL_Services('GetOITableToSQLMap', OITableName)
            If SRP_JSON(MapObj, 'PARSE', OITableToSQLMap) EQ '' then
                PrimarySQLTable = SRP_JSON(MapObj, 'GETVALUE', 'PrimarySQLTable.Name')
                SRP_JSON(MapObj, 'RELEASE')
            end
            If PrimarySQLTable NE '' then Memory_Services('SetValue', ServiceKeyID, PrimarySQLTable)
        end else
            Error_Services('Add', 'The OITableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = PrimarySQLTable

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSecondarySQLTables
//
// Returns an @FM delimited list of the secondary SQL tables that the indicated OpenInsight table is mapped to.
//----------------------------------------------------------------------------------------------------------------------
Service GetSecondarySQLTables(OITableName)

    Convert @Lower_Case to @Upper_Case in OITableName
    ServiceKeyID       := '*' : OITableName
    SecondarySQLTables  = Memory_Services('GetValue', ServiceKeyID)

    If SecondarySQLTables EQ '' then
        If OITableName NE '' then
            OITableToSQLMap = SQL_Services('GetOITableToSQLMap', OITableName)
            If SRP_JSON(MapObj, 'PARSE', OITableToSQLMap) EQ '' then
                TablesArray = SRP_JSON(MapObj, 'GET', 'SecondarySQLTables')
                NumTables   = SRP_JSON(TablesArray, 'GETCOUNT')
                MatchFound  = False$
                For TableNum = 1 to NumTables
                    SecondarySQLTables := SRP_JSON(TablesArray, 'GETVALUE', '[' : TableNum : '].Name', '') : @FM
                Until MatchFound
                Next TableNum
                SRP_JSON(TablesArray, 'RELEASE')
                SRP_JSON(MapObj, 'RELEASE')
            end
            SecondarySQLTables[-1, 1]   = ''
            If SecondarySQLTables NE '' then Memory_Services('SetValue', ServiceKeyID, SecondarySQLTables)
        end else
            Error_Services('Add', 'The OITableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = SecondarySQLTables

end service


//----------------------------------------------------------------------------------------------------------------------
// GetStaticSQLColumnNames
//
// Returns an @FM delimited list of SQL column names that have a static value. These are usually used in a SQL insert
// statement along with the mapped columns.
//----------------------------------------------------------------------------------------------------------------------
Service GetStaticSQLColumnNames(OITableName)

    Convert @Lower_Case to @Upper_Case in OITableName
    ServiceKeyID           := '*' : OITableName
*    StaticSQLColumnNames    = Memory_Services('GetValue', ServiceKeyID)
    StaticSQLColumnNames    = ''

    If StaticSQLColumnNames EQ '' then
        If (OITableName NE '') then
            // Add all OpenInsight columns defined in the SQL Join Clause.
            OITableToSQLMap = SQL_Services('GetOITableToSQLMap', OITableName)
            If SRP_JSON(MapObj, 'PARSE', OITableToSQLMap) EQ '' then
                StaticColumnsArray  = SRP_JSON(MapObj, 'GET', 'StaticColumns')
                NumColumns          = SRP_JSON(StaticColumnsArray, 'GETCOUNT')
                For ColumnNum = 1 to NumColumns
                    objStaticColumn         = SRP_JSON(StaticColumnsArray, 'GET', '[' : ColumnNum : ']')
                    StaticSQLColumnName     = SRP_JSON(objStaticColumn, 'GETMEMBERS')
                    If StaticSQLColumnName[1, 1] EQ '(' then
                        Convert '(' to '[' in StaticSQLColumnName
                        Convert ')' to ']' in StaticSQLColumnName
                    end
                    StaticSQLColumnNames   := StaticSQLColumnName : @FM
                    SRP_JSON(objStaticColumn, 'RELEASE')
                Next NumColumns
                StaticSQLColumnNames[-1, 1] = ''
                SRP_JSON(StaticColumnsArray, 'RELEASE')
                SRP_JSON(MapObj, 'RELEASE')
            end
            If StaticSQLColumnNames NE '' then Memory_Services('SetValue', ServiceKeyID, StaticSQLColumnNames)
        end else
            Error_Services('Add', 'The OITableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = StaticSQLColumnNames

end service


//----------------------------------------------------------------------------------------------------------------------
// GetStaticSQLColumnValues
//
// Returns an @FM delimited list of values for SQL column names that have a static value. These are usually used in a
// SQL insert statement along with the mapped columns.
//----------------------------------------------------------------------------------------------------------------------
Service GetStaticSQLColumnValues(OITableName)

    Convert @Lower_Case to @Upper_Case in OITableName
    ServiceKeyID           := '*' : OITableName
    StaticSQLColumnValues   = Memory_Services('GetValue', ServiceKeyID)

    If StaticSQLColumnValues EQ '' then
        If (OITableName NE '') then
            // Add all OpenInsight columns defined in the SQL Join Clause.
            OITableToSQLMap = SQL_Services('GetOITableToSQLMap', OITableName)
            If SRP_JSON(MapObj, 'PARSE', OITableToSQLMap) EQ '' then
                StaticColumnsArray  = SRP_JSON(MapObj, 'GET', 'StaticColumns')
                NumColumns          = SRP_JSON(StaticColumnsArray, 'GETCOUNT')
                For ColumnNum = 1 to NumColumns
                    objStaticColumn         = SRP_JSON(StaticColumnsArray, 'GET', '[' : ColumnNum : ']')
                    StaticSQLColumnName     = SRP_JSON(objStaticColumn, 'GETMEMBERS')
                    StaticValue             = SRP_JSON(objStaticColumn, 'GETVALUE', StaticSQLColumnName, '')
                    StaticValue             = "'" : StaticValue : "'"
                    StaticSQLColumnValues  := StaticValue : @FM
                    SRP_JSON(objStaticColumn, 'RELEASE')
                Next NumColumns
                StaticSQLColumnValues[-1, 1]    = ''
                SRP_JSON(StaticColumnsArray, 'RELEASE')
                SRP_JSON(MapObj, 'RELEASE')
            end
            If StaticSQLColumnValues NE '' then Memory_Services('SetValue', ServiceKeyID, StaticSQLColumnValues)
        end else
            Error_Services('Add', 'The OITableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = StaticSQLColumnValues

end service


//----------------------------------------------------------------------------------------------------------------------
// GetAMVMaster
//
// Returns the name of the OpenInsight column used as the AMV (associated multivalue) master used to determine the
// number of values needed to write into the secondary SQL table.
//----------------------------------------------------------------------------------------------------------------------
Service GetAMVMaster(OITableName, SQLTableName)

    Convert @Lower_Case to @Upper_Case in OITableName
    Convert @Lower_Case to @Upper_Case in SQLTableName
    ServiceKeyID   := '*' : OITableName : '*' : SQLTableName
    AMVMaster       = Memory_Services('GetValue', ServiceKeyID)

    If AMVMaster EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            OITableToSQLMap = SQL_Services('GetOITableToSQLMap', OITableName)
            JoinArray       = ''    ; // Initialize the array.
            If SRP_JSON(MapObj, 'PARSE', OITableToSQLMap) EQ '' then
                PrimarySQLTableName = SRP_JSON(MapObj, 'GETVALUE', 'PrimarySQLTable.Name')
                If SQLTableName _EQC PrimarySQLTableName then
                    // Primary SQL tables do not have a Seq Key. Just return the empty string.
                end else
                    // Look for a matching secondary SQL table.
                    TablesArray = SRP_JSON(MapObj, 'GET', 'SecondarySQLTables')
                    NumTables   = SRP_JSON(TablesArray, 'GETCOUNT')
                    MatchFound  = False$
                    For TableNum = 1 to NumTables
                        SecondarySQLTableName   = SRP_JSON(TablesArray, 'GETVALUE', '[' : TableNum : '].Name', '')
                        If SQLTableName _EQC SecondarySQLTableName then
                            AMVMaster   = SRP_JSON(TablesArray, 'GETVALUE', '[' : TableNum : '].AMVMaster', '')
                            Swap '<none>' with '' in AMVMaster
                            MatchFound  = True$
                        end
                    Until MatchFound
                    Next TableNum
                    SRP_JSON(TablesArray, 'RELEASE')
                end
                SRP_JSON(MapObj, 'RELEASE')
            end

            If AMVMaster NE '' then Memory_Services('SetValue', ServiceKeyID, AMVMaster)
        end else
            Error_Services('Add', 'The OITableName or SQLTableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = AMVMaster

end service


//----------------------------------------------------------------------------------------------------------------------
// GetJoinedOIColumns
//
// Returns an @FM list of column names from the indicated OpenInsight table that are used to join with the indicated
// SQL table.
//----------------------------------------------------------------------------------------------------------------------
Service GetJoinedOIColumns(OITableName, SQLTableName)

    Convert @Lower_Case to @Upper_Case in OITableName
    Convert @Lower_Case to @Upper_Case in SQLTableName
    ServiceKeyID   := '*' : OITableName : '*' : SQLTableName
    JoinedOIColumns = Memory_Services('GetValue', ServiceKeyID)

    If JoinedOIColumns EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            OITableToSQLMap = SQL_Services('GetOITableToSQLMap', OITableName)
            JoinArray       = ''    ; // Initialize the array.
            If SRP_JSON(MapObj, 'PARSE', OITableToSQLMap) EQ '' then
                PrimarySQLTableName = SRP_JSON(MapObj, 'GETVALUE', 'PrimarySQLTable.Name')
                If SQLTableName _EQC PrimarySQLTableName then
                    // Use the join array from the primary SQL Table.
                    JoinArray   = SRP_JSON(MapObj, 'GET', 'PrimarySQLTable.JoinClause')
                end else
                    // Look for a matching secondary SQL table.
                    TablesArray = SRP_JSON(MapObj, 'GET', 'SecondarySQLTables')
                    NumTables   = SRP_JSON(TablesArray, 'GETCOUNT')
                    MatchFound  = False$
                    For TableNum = 1 to NumTables
                        TableObj                = SRP_JSON(TablesArray, 'GET', '[' : TableNum : ']')
                        SecondarySQLTableName   = SRP_JSON(TableObj, 'GETVALUE', 'Name', '')
                        If SQLTableName _EQC SecondarySQLTableName then
                            JoinArray       = SRP_JSON(TableObj, 'GET', 'JoinClause')
                            MatchFound      = True$
                        end
                        SRP_JSON(TableObj, 'RELEASE')
                    Until MatchFound
                    Next TableNum
                    SRP_JSON(TablesArray, 'RELEASE')
                end
                SRP_JSON(MapObj, 'RELEASE')
            end

            If JoinArray GT 0 then
                NumColumns  = SRP_JSON(JoinArray, 'GETCOUNT')
                For ColumnNo = 1 to NumColumns
                    OIColumnName        = SRP_JSON(JoinArray, 'GETVALUE', '[' : ColumnNo : ']' : 'OIColumnName', '')
                    JoinedOIColumns    := OIColumnName : @FM
                Next ColumnNo
                JoinedOIColumns[-1, 1]  = ''
                SRP_JSON(JoinArray, 'RELEASE')
            end

            If JoinedOIColumns NE '' then Memory_Services('SetValue', ServiceKeyID, JoinedOIColumns)
        end else
            Error_Services('Add', 'The OITableName or SQLTableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = JoinedOIColumns

end service


//----------------------------------------------------------------------------------------------------------------------
// GetJoinClause
//
// Returns a well formed join clause using the indicated OpenInsight Key ID and row for the indicated OpenInsight table
// and SQL table. Note: OIRow will likely be an empty string since most join clauses will be based on the OpenInsight
// Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service GetJoinClause(OITableName, SQLTableName, OIKeyID, OIRow)

    Convert @Lower_Case to @Upper_Case in OITableName
    Convert @Lower_Case to @Upper_Case in SQLTableName
    JoinClause  = ''

    If (OITableName NE '') AND (SQLTableName NE '') AND (OIKeyID NE '') then
        OITableToSQLMap = SQL_Services('GetOITableToSQLMap', OITableName)
        If Error_Services('NoError') then
            FieldArray  = Database_Services('ReadDataRow', 'DICT.' : OITablename, '%FIELDS%')
        end
        If Error_Services('NoError') then
            JoinArray       = ''    ; // Initialize the array.
            If SRP_JSON(MapObj, 'PARSE', OITableToSQLMap) EQ '' then
                PrimarySQLTableName = SRP_JSON(MapObj, 'GETVALUE', 'PrimarySQLTable.Name')
                If SQLTableName _EQC PrimarySQLTableName then
                    // Use the join array from the primary SQL Table.
                    JoinArray   = SRP_JSON(MapObj, 'GET', 'PrimarySQLTable.JoinClause')
                end else
                    // Look for a matching secondary SQL table.
                    TablesArray = SRP_JSON(MapObj, 'GET', 'SecondarySQLTables')
                    NumTables   = SRP_JSON(TablesArray, 'GETCOUNT')
                    MatchFound  = False$
                    For TableNum = 1 to NumTables
                        TableObj                = SRP_JSON(TablesArray, 'GET', '[' : TableNum : ']')
                        SecondarySQLTableName   = SRP_JSON(TableObj, 'GETVALUE', 'Name', '')
                        If SQLTableName _EQC SecondarySQLTableName then
                            JoinArray       = SRP_JSON(TableObj, 'GET', 'JoinClause')
                            MatchFound      = True$
                        end
                        SRP_JSON(TableObj, 'RELEASE')
                    Until MatchFound
                    Next TableNum
                    SRP_JSON(TablesArray, 'RELEASE')
                end
                SRP_JSON(MapObj, 'RELEASE')
            end

            If JoinArray GT 0 then
                NumColumns  = SRP_JSON(JoinArray, 'GETCOUNT')
                For ColumnNo = 1 to NumColumns
                    JoinClause     := SRP_JSON(JoinArray, 'GETVALUE', '[' : ColumnNo : ']' : 'SQLColumnName', '') : ' '
                    JoinClause     := SRP_JSON(JoinArray, 'GETVALUE', '[' : ColumnNo : ']' : 'Operator', '') : ' '
                    OIColumnName    = SRP_JSON(JoinArray, 'GETVALUE', '[' : ColumnNo : ']' : 'OIColumnName', '')
                    Locate OIColumnName in FieldArray<FIELDS_NAME$> using @VM setting vPos then
                        OIColumnType    = FieldArray<FIELDS_TYPE$, vPos>
                        If OIColumnType EQ 'F' then
                            OIColumnPos = FieldArray<FIELDS_FIELD_NO$, vPos>
                            If OIColumnPos GT 0 then
                                OIColumnValue   = OIRow<OIColumnPos>
                            end else
                                OIKeyPart       = FieldArray<FIELDS_PART$, vPos>
                                OIColumnValue   = Field(OIKeyID, '*', OIKeyPart, 1)
                            end
                        end else
*                            OIColumnValue   = Calculate(OIColumnName)    ; // Actual logic used in MFS
                            OIColumnValue   = Xlate(OITableName, OIKeyID, OIColumnName, 'X')
                        end
                    end else
                        OIColumnValue   = ''
                    end
                    JoinClause := "'" : OIColumnValue : "'"
                    JoinClause := @FM
                Next ColumnNo

                JoinClause[-1, 1]   = ''
                Swap @FM with ' AND ' in JoinClause
                SRP_JSON(JoinArray, 'RELEASE')
            end
        end
    end else
        Error_Services('Add', 'The OITableName, SQLTableName, OIKeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = JoinClause

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedOIColumnNames
//
// Returns an @FM delimited list of OpenInsight column names from the indicated table that are mapped to the indicated
// SQL table.
//----------------------------------------------------------------------------------------------------------------------
Service GetMappedOIColumnNames(OITableName, SQLTableName)

    Convert @Lower_Case to @Upper_Case in OITableName
    Convert @Lower_Case to @Upper_Case in SQLTableName
    ServiceKeyID       := '*' : OITableName : '*' : SQLTableName
    MappedOIColumnNames = Memory_Services('GetValue', ServiceKeyID)

    If MappedOIColumnNames EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            // Add all OpenInsight columns defined in the SQL Join Clause.
            JoinedOIColumns = SQL_Services('GetJoinedOIColumns', OITableName, SQLTableName)
            If JoinedOIColumns NE '' then
                Convert @VM to @FM in JoinedOIColumns
                MappedOIColumnNames    := JoinedOIColumns : @FM
            end

            // Go through all of the OpenInsight column SQL maps and add any column which is bound to the same SQL
            // table. Note, one or more of these columns might already be in the list from the GetJoinedOIColumns service.
            // These will be skipped.
            AllOIColumnToSQLMaps    = SQL_Services('GetAllOIColumnToSQLMaps', OITableName)
            For Each OIColumnToSQLMap in AllOIColumnToSQLMaps using @FM setting fPos
                If SRP_JSON(MapObj, 'PARSE', OIColumnToSQLMap<0, 2>) EQ '' then
                    MappedSQLTableName  = SRP_JSON(MapObj, 'GETVALUE', 'SQLTableName')
                    If (MappedSQLTableName NE '') AND (SQLTableName EQ MappedSQLTableName) then
                        OIColumnName    = OIColumnToSQLMap<0, 1>
                        Locate OIColumnName in MappedOIColumnNames using @FM setting fPos else
                            MappedOIColumnNames    := OIColumnName : @FM
                        end
                    end
                end
            Next OIColumnToSQLMap

            MappedOIColumnNames[-1, 1] = ''
            If MappedOIColumnNames NE '' then Memory_Services('SetValue', ServiceKeyID, MappedOIColumnNames)
        end else
            Error_Services('Add', 'The OITableName or SQLTableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = MappedOIColumnNames

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedSQLColumnNames
//
// Returns an @FM delimited list of SQL column names from the indicated table that are mapped from the indicated
// OpenInsight table and associated OpenInsight columns.
//----------------------------------------------------------------------------------------------------------------------
Service GetMappedSQLColumnNames(OITableName, SQLTableName, OIColumns)

    Convert @Lower_Case to @Upper_Case in OITableName
    Convert @Lower_Case to @Upper_Case in SQLTableName
    ServiceKeyID           := '*' : OITableName : '*' : SQLTableName
*    MappedSQLColumnNames    = Memory_Services('GetValue', ServiceKeyID)
    MappedSQLColumnNames    = ''

    If MappedSQLColumnNames EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            OIColumnNames   = SQL_Services('GetMappedOIColumnNames', OITableName, SQLTableName)
            If OIColumns NE '' then
                OIColumnNames   = SRP_Array('Join', OIColumnNames, OIColumns, 'AND', @FM)
            end
            For Each OIColumnName in OIColumnNames using @FM setting fPos
                OIColumnToSQLMap    = SQL_Services('GetOIColumnToSQLMap', OITableName, OIColumnName)
                If Error_Services('NoError') then
                    If SRP_JSON(MapObj, 'PARSE', OIColumnToSQLMap) EQ '' then
                        MappedSQLColumnNames   := SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnName') : @FM
                        SRP_JSON(MapObj, 'RELEASE')
                    end
                end
            Next OIColumnName

            MappedSQLColumnNames[-1, 1] = ''
            If MappedSQLColumnNames NE '' then Memory_Services('SetValue', ServiceKeyID, MappedSQLColumnNames)
        end else
            Error_Services('Add', 'The OITableName or SQLTableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = MappedSQLColumnNames

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedSQLColumnValues
//
// Returns an @FM delimited list of SQL column values for the indicated table that are derived from the indicated
// OpenInsight table and associated OpenInsight columns. This service is normally called to create a fully formed Insert
// query statement.
//----------------------------------------------------------------------------------------------------------------------
Service GetMappedSQLColumnValues(OITableName, SQLTableName, OIKeyID, OIRow, AMVPosition, OIColumns)

    Convert @Lower_Case to @Upper_Case in OITableName
    Convert @Lower_Case to @Upper_Case in SQLTableName
    If Num(AMVPosition) else AMVPosition = ''

    MappedSQLColumnValues   = ''

    If (OITableName NE '') AND (SQLTableName NE '') AND (OIKeyID NE '') AND (OIRow NE '') then
        FieldArray      = Database_Services('ReadDataRow', 'DICT.' : OITablename, '%FIELDS%')
        OIColumnNames   = SQL_Services('GetMappedOIColumnNames', OITableName, SQLTableName)
        If OIColumns NE '' then
            OIColumnNames   = SRP_Array('Join', OIColumnNames, OIColumns, 'AND', @FM)
        end
        If OIColumnNames NE '' then
            For Each OIColumnName in OIColumnNames using @FM setting fPos
                Locate OIColumnName in FieldArray<FIELDS_NAME$> using @VM setting vPos then
                    OIColumnToSQLMap    = SQL_Services('GetOIColumnToSQLMap', OITableName, OIColumnName)
                    If Error_Services('NoError') then
                        If SRP_JSON(MapObj, 'PARSE', OIColumnToSQLMap) EQ '' then
                            SQLColumnType   = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnType')
                            OIColumnType    = FieldArray<FIELDS_TYPE$, vPos>
                            If OIColumnType EQ 'F' then
                                OIColumnPos = FieldArray<FIELDS_FIELD_NO$, vPos>
                                If OIColumnPos GT 0 then
                                    OIColumnValue   = OIRow<OIColumnPos>
                                end else
                                    OIKeyPart       = FieldArray<FIELDS_PART$, vPos>
                                    OIColumnValue   = Field(OIKeyID, '*', OIKeyPart, 1)
                                    If OIColumnValue EQ '' then
                                        Error_Services('Add', 'Key ID Column ' : OIColumnName : ' was assigned an empty string value in ' : Service : '.')
                                    end
                                end
                            end else
                                // OIColumnValue   = Calculate(OIColumnName)    ; // Actual logic used in MFS
                                OIColumnValue   = Xlate(OITablename, OIKeyID, OIColumnName, 'X')
                            end
                            If AMVPosition NE '' then
                                OIColumnValue   = OIColumnValue<0, AMVPosition>
                            end
                            Begin Case
                                Case OIColumnValue EQ ''
                                    // Empty strings are treated as NULL values.
                                    OIColumnValue   = 'NULL'
                                Case SQLColumnType[1, 7] _EQC 'VARCHAR' OR SQLColumnType _EQC 'CHAR'
                                    // Single quote the value.
                                    SQLColumnLength = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnLength')
                                    If (SQLColumnLength EQ '') OR (SQLColumnLength EQ -1) then SQLColumnLength = Len(OIColumnValue)
                                    OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                    If OIColumnConv NE '' then
                                        OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                    end
                                    OIColumnValue   = OIColumnValue[1, SQLColumnLength, 1]
                                    Swap "'" with "''" in OIColumnValue
                                    OIColumnValue   = "'" : OIColumnValue : "'"
                                Case SQLColumnType _EQC 'INT'
                                    // Oconv based on the OI column output conversion if it exists.
                                    OIColumnConv    = 'MD0'
                                    If OIColumnConv NE '' then
                                        OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                    end
                                Case SQLColumnType _EQC 'NUMBER'
                                    // Oconv based on the OI column output conversion if it exists.
                                    OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                    If OIColumnConv NE '' then
                                        OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                    end
                                Case SQLColumnType _EQC 'DECIMAL'
                                    // Oconv based on the OI column type.
                                    // Oconv based on the OI column output conversion if it exists.
                                    OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                    If OIColumnConv NE '' then
                                        OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                    end
                                Case SQLColumnType _EQC 'DATE'
                                    OIColumnValue   = "TO_DATE('" : Oconv(OIColumnValue, 'D4/J') : "', 'yyyy/mm/dd')"
                                Case Otherwise$
                                    // Treat as a VARCHAR2 and single quote the value.
                                    SQLColumnLength = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnLength')
                                    If SQLColumnLength EQ '' then SQLColumnLength = Len(OIColumnValue)
                                    OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                    If OIColumnConv NE '' then
                                        OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                    end
                                    OIColumnValue   = OIColumnValue[1, SQLColumnLength, 1]
                                    Swap "'" with "''" in OIColumnValue
                                    OIColumnValue   = "'" : OIColumnValue : "'"
                            End Case
                            // Basic conversion rules.
                            Convert \09\ to ' ' in OIColumnValue
                            Swap \0D0A\ with ' ' in OIColumnValue
                            Convert @VM to '|' in OIColumnValue
                            Convert @SVM to ' ' in OIColumnValue
                            Convert @TM to ' ' in OIColumnValue
                            Convert @STM to ' ' in OIColumnValue
                            MappedSQLColumnValues  := OIColumnValue : @FM

                            SRP_JSON(MapObj, 'RELEASE')
                        end
                    end
                end
            While Error_Services('NoError')
            Next OIColumnName
        end
        MappedSQLColumnValues[-1, 1]    = ''
    end else
        Error_Services('Add', 'The OITableName, SQLTableName, OIKeyID, or OIRow argument was missing in the ' : Service : ' service.')
    end

    Response    = MappedSQLColumnValues

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSQLDeleteStatements
//
// Returns an @FM delimited list of SQL delete statements for the SQL table mapped to the indicated OpenInsight table.
// This will automatically create all SQL delete statements for secondary SQL tables as well. Note: OIRow will likely be
// an empty string since most join clauses will be based on the OpenInsight Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service GetSQLDeleteStatements(OITableName, OIKeyID, OIRow)

    Convert @Lower_Case to @Upper_Case in OITableName
    SQLDeleteStatements = ''

    If (OITableName NE '') AND (OIKeyID NE '') then

        // Start with the primary SQL table that is mapped to this OpenInsight table.
        PrimarySQLTableName     = SQL_Services('GetPrimarySQLTable', OITableName)
        JoinClause              = SQL_Services('GetJoinClause', OITableName, PrimarySQLTableName, OIKeyID, OIRow)
        SQLDeleteStatement      = ''
        SQLDeleteStatement     := 'DELETE FROM ' : PrimarySQLTableName : ' '
        SQLDeleteStatement     := 'WHERE '
        SQLDeleteStatement     := JoinClause
        SQLDeleteStatements    := SQLDeleteStatement : @FM

        // Add any secondary SQL table inserts that might be required.
        SecondarySQLTableNames  = SQL_Services('GetSecondarySQLTables', OITableName)
        If SecondarySQLTableNames NE '' then
            For Each SecondarySQLTableName in SecondarySQLTableNames using @FM
                JoinClause              = SQL_Services('GetJoinClause', OITableName, SecondarySQLTableName, OIKeyID, OIRow)
                SQLDeleteStatement      = ''
                SQLDeleteStatement     := 'DELETE FROM ' : SecondarySQLTableName : ' '
                SQLDeleteStatement     := 'WHERE '
                SQLDeleteStatement     := JoinClause
                SQLDeleteStatements    := SQLDeleteStatement : @FM
            Next SecondarySQLTableName
        end

        SQLDeleteStatements[-1, 1]  = ''
    end else
        Error_Services('Add', 'The OITableName or OIKeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = SQLDeleteStatements

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSQLInsertStatements
//
// Returns an @FM delimited list of SQL insert statements for the SQL table mapped to the indicated OpenInsight table.
// This will automatically create all SQL insert statements for secondary SQL tables as well.
//----------------------------------------------------------------------------------------------------------------------
Service GetSQLInsertStatements(OITableName, OIKeyID, OIRow, OIColumns)

    Convert @Lower_Case to @Upper_Case in OITableName
    SQLInsertStatements = ''

    If (OITableName NE '') AND (OIKeyID NE '') AND (OIRow NE '') then

        // Start with the primary SQL table that is mapped to this OpenInsight table.
        PrimarySQLTableName     = SQL_Services('GetPrimarySQLTable', OITableName)
        MappedSQLColumnNames    = SQL_Services('GetMappedSQLColumnNames', OITableName, PrimarySQLTableName, OIColumns)
        MappedSQLColumnValues   = SQL_Services('GetMappedSQLColumnValues', OITablename, PrimarySQLTableName, OIKeyID, OIRow, '', OIColumns)
        StaticSQLColumnNames    = SQL_Services('GetStaticSQLColumnNames', OITableName)
        If StaticSQLColumnNames NE '' then
            MappedSQLColumnNames   := @FM : StaticSQLColumnNames
            StaticSQLColumnValues   = SQL_Services('GetStaticSQLColumnValues', OITableName)
            MappedSQLColumnValues  := @FM : StaticSQLColumnValues
        end
        Swap @FM with ', ' in MappedSQLColumnNames
        Swap @FM with ", " in MappedSQLColumnValues
        SQLInsertStatement      = ''
        SQLInsertStatement     := 'INSERT INTO ' : PrimarySQLTableName : ' '
        SQLInsertStatement     := '('
        SQLInsertStatement     := MappedSQLColumnNames
        SQLInsertStatement     := ') VALUES ('
        SQLInsertStatement     := MappedSQLColumnValues
        SQLInsertStatement     := ')'

        SQLInsertStatements    := SQLInsertStatement : @FM

        If Error_Services('NoError') then
            // Add any secondary SQL table inserts that might be required.
            SecondarySQLTableNames  = SQL_Services('GetSecondarySQLTables', OITableName)
            If SecondarySQLTableNames NE '' then
                For Each SecondarySQLTableName in SecondarySQLTableNames using @FM
                    AMVMaster           = SQL_Services('GetAMVMaster', OITableName, SecondarySQLTableName)
                    FieldArray          = Database_Services('ReadDataRow', 'DICT.' : OITablename, '%FIELDS%')
                    If Error_Services('NoError') then
                        Locate AMVMaster in FieldArray<FIELDS_NAME$> using @VM setting vPos then
                            OIColumnType    = FieldArray<FIELDS_TYPE$, vPos>
                            If OIColumnType EQ 'F' then
                                OIColumnPos = FieldArray<FIELDS_FIELD_NO$, vPos>
                                If OIColumnPos GT 0 then
                                    OIColumnValue   = OIRow<OIColumnPos>
                                end else
                                    OIKeyPart       = FieldArray<FIELDS_PART$, vPos>
                                    OIColumnValue   = Field(OIKeyID, '*', OIKeyPart, 1)
                                end
                            end else
                                // OIColumnValue   = Calculate(OIColumnName)    ; // Actual logic used in MFS
                                OIColumnValue   = Xlate(OITablename, OIKeyID, OIColumnName, 'X')
                            end
                            NumPositions    = DCount(OIColumnValue, @VM)
                        end else
                            NumPositions    = 1
                        end
                    end else
                        NumPositions    = 1
                    end

                    For AMVPosition = 1 to NumPositions
                        MappedSQLColumnNames    = ''
                        MappedSQLColumnValues   = ''

                        MappedSQLColumnNames   := SQL_Services('GetMappedSQLColumnNames', OITableName, SecondarySQLTableName) : @FM
                        MappedSQLColumnValues  := SQL_Services('GetMappedSQLColumnValues', OITablename, SecondarySQLTableName, OIKeyID, OIRow, AMVPosition) : @FM

                        MappedSQLColumnNames[-1, 1]     = ''
                        MappedSQLColumnValues[-1, 1]    = ''

                        Swap @FM with ', ' in MappedSQLColumnNames
                        Swap @FM with ", " in MappedSQLColumnValues
                        SQLInsertStatement      = ''
                        SQLInsertStatement     := 'INSERT INTO ' : SecondarySQLTableName : ' '
                        SQLInsertStatement     := '('
                        SQLInsertStatement     := MappedSQLColumnNames
                        SQLInsertStatement     := ') VALUES ('
                        SQLInsertStatement     := MappedSQLColumnValues
                        SQLInsertStatement     := ')'

                        SQLInsertStatements    := SQLInsertStatement : @FM            
                    Next AMVPosition
                Next SecondarySQLTableName
            end
        end

        SQLInsertStatements[-1, 1]  = ''
    end else
        Error_Services('Add', 'The OITableName, OIKeyID, or OIRow argument was missing in the ' : Service : ' service.')
    end

    Response    = SQLInsertStatements

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSQLSelectStatement
//
// Returns the SQL select statement for the indicated SQL table based on its mapping to the indicated OpenInsight table.
//----------------------------------------------------------------------------------------------------------------------
Service GetSQLSelectStatement(OITableName, SQLTableName, OIKeyID)

    Convert @Lower_Case to @Upper_Case in OITableName
    Convert @Lower_Case to @Upper_Case in SQLTableName
    SQLSelectStatement  = ''

    If (OITableName NE '') AND (SQLTableName NE '') AND (OIKeyID NE '') then

        JoinClause              = SQL_Services('GetJoinClause', OITableName, SQLTableName, OIKeyID)
        MappedSQLColumnNames    = SQL_Services('GetMappedSQLColumnNames', OITableName, SQLTableName)
        Swap @FM with ', ' in MappedSQLColumnNames
        SQLSelectStatement      = ''
        SQLSelectStatement     := 'SELECT '
        SQLSelectStatement     := MappedSQLColumnNames : ' '
        SQLSelectStatement     := 'FROM ' : SQLTableName : ' '
        SQLSelectStatement     := 'WHERE '
        SQLSelectStatement     := JoinClause : ' '

    end else
        Error_Services('Add', 'The OITableName, SQLTableName, or OIKeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = SQLSelectStatement

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedOIColumnValues
//
// Returns an OpenInsight data row for the indicated table that are derived from the indicated SQL table and
// corresponding SQL data. Note, since data can come from multiple SQL tables, the OIRow arguments can already contain
// data from a previous call to this service. This allows primary and secondary SQL tables to be passed into this
// service so that all relevant SQL data can be denormalized into a single OpenInsight data row. This service is
// normally called to create a denormalized OpenInsight row.
//----------------------------------------------------------------------------------------------------------------------
Service GetMappedOIColumnValues(OITableName, SQLTableName, SQLData, OIRow)

    Convert @Lower_Case to @Upper_Case in OITableName
    Convert @Lower_Case to @Upper_Case in SQLTableName

    If (OITableName NE '') AND (SQLTableName NE '') AND (SQLData NE '') then
        MappedOIColumnNames     = SQL_Services('GetMappedOIColumnNames', OITableName, SQLTableName)
        MappedSQLColumnNames    = SQL_Services('GetMappedSQLColumnNames', OITableName, SQLTableName)
        FieldArray              = Database_Services('ReadDataRow', 'DICT.' : OITablename, '%FIELDS%')
        If Error_Services('NoError') then
            For Each SQLRow in SQLData using @RM setting ValuePos
                For Each SQLColumnName in MappedSQLColumnNames using @FM setting fPos
                    OIColumnName    = MappedOIColumnNames<fPos>
                    Locate OIColumnName in FieldArray<FIELDS_NAME$> using @VM setting vPos then
                        OIColumnType    = FieldArray<FIELDS_TYPE$, vPos>
                        OIColumnPos     = FieldArray<FIELDS_FIELD_NO$, vPos>
                        If OIColumnType EQ 'F' AND OIColumnPos NE 0 then
                            OIColumnToSQLMap    = SQL_Services('GetOIColumnToSQLMap', OITableName, OIColumnName)
                            If Error_Services('NoError') then
                                If SRP_JSON(MapObj, 'PARSE', OIColumnToSQLMap) EQ '' then
                                    SQLColumnType   = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnType')
                                    OIColumnValue   = SQLRow<fPos>
                                    Swap '<@VM>' with @VM in OIColumnValue
                                    Swap @SVM with '<@SVM>' in OIColumnValue
                                    Swap @TM with '<@TM>' in OIColumnValue
                                    Swap @STM with '<@STM>' in OIColumnValue
                                    Begin Case
                                        Case OIColumnValue EQ '<NULL>'
                                            // SQL <NULL> values are empty strings.
                                            OIColumnValue   = ''
                                        Case SQLColumnType _EQC 'NUMBER'
                                            // Oconv based on the OI column output conversion if it exists.
                                            OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                            If OIColumnConv NE '' then
                                                OIColumnValue   = Iconv(OIColumnValue, OIColumnConv)
                                            end
                                        Case SQLColumnType _EQC 'DECIMAL'
                                            // Oconv based on the OI column type.
                                            // Oconv based on the OI column output conversion if it exists.
                                            OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                            If OIColumnConv NE '' then
                                                OIColumnValue   = Iconv(OIColumnValue, OIColumnConv)
                                            end
                                        Case SQLColumnType _EQC 'DATE'
                                            // Oconv based on the OI column output conversion if it exists, but only if
                                            // the data is non-numeric.
                                            If Alpha(OIColumnValue) then
                                                OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                                If OIColumnConv NE '' then
                                                    OIColumnValue   = Iconv(OIColumnValue, OIColumnConv)
                                                end
                                            end
                                    End Case
                                    OIRow<OIColumnPos, ValuePos>    = OIColumnValue
                                end
                                SRP_JSON(MapObj, 'RELEASE')
                            end
                        end
                    end
                Next SQLColumnName
            Next SQLRow
        end

    end else
        Error_Services('Add', 'The SQLTableName, SQLData, or OITableName argument was missing in the ' : Service : ' service.')
    end

    Response    = OIRow

end service


//----------------------------------------------------------------------------------------------------------------------
// LogConnectionActivity
//
// Appends the activity log for the Connection Object.
//----------------------------------------------------------------------------------------------------------------------
Service LogConnectionActivity(Activity)

    If Activity NE '' then
        KeyID   = '%SQL_CONNECTION_ACTIVITY_LOG%'
        ActivityLog = Database_Services('ReadDataRow', 'CONTROL', KeyID)
        If ActivityLog EQ '' then
            Transfer Activity to ActivityLog
        end else
            ActivityLog := @FM  :Activity
        end
        Database_Services('WriteDataRow', 'CONTROL', KeyID, ActivityLog, True$)
    end else
        Error_Services('Add', 'The Activity argument was missing in the ' : Service : ' service.')
    end

end service


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



