Function SQL_Services_RevDotNet(@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_RevDotNet

    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

    History     :   (Date, Initials, Notes)
        06/15/21    djs     v1.0.0 - Module cloned from SQL_Services v2.0.8. Converted module to use RevDotNet
                                     instead of SRP_COM.

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

#pragma precomp SRP_PreCompiler

$insert LOGICAL
$insert SERVICE_SETUP
$insert SERVERS_EQUATES
$insert MICROSOFT_ADO_EQUATES
$insert DICT_EQUATES
$insert REVDOTNETEQUATES

Common /SQLServicesRevDotNet/ ConnectionObjects@, CommandObjects@, TransactionObjects@, Unused4@, Unused5@, Unused6@, Unused7@, Unused8@

Declare function    Memory_Services, SQL_Services_RevDotNet, Database_Services, SRP_JSON, SRP_Array, SRP_Hash, SRP_COM, SRP_Encode
Declare function    SRP_Date, SRP_DateTime, SRP_Time, Utility, Unicode_UTF8, Str_Unicode, GetTickCount, SRP_Decode
Declare function    SRP_List
Declare subroutine  Memory_Services, SQL_Services_RevDotNet, Database_Services, SRP_JSON, SRP_Stopwatch, SRP_COM, SRP_List
Declare subroutine  GetTempPath, GetTempFileName, Sleepery, WinYield, Yield, RList, Set_Status

If ConnectionObjects@ EQ '' then
    ConnectionObjects@   = SRP_List('Create')
    CommandObjects@      = SRP_List('Create')
    TransactionObjects@  = SRP_List('Create')
end

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
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

//----------------------------------------------------------------------------------------------------------------------
// WriteDataRowToSQL
//
// OITableName  - The name of the OpenInsight table that is mapped to the SQL table being written to. - [Required]
// OIKeyID      - The Key ID to the OpenInsight row being written to SQL. - [Required]
// OIRow        - The OpenInsight row being written to SQL. - [Required]
// OIColumns    - An @FM delimited list of OpenInsight table columns that should only be used to write to SQL. If empty,
//                all columns that have a map defintion will be used. - [Optional]
// 
// 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_RevDotNet('GetMappedSQLServer', OITableName)
        objConnection      = SQL_Services_RevDotNet('GetConnectionObject', SQLServer, 2)
        TransactionQueries = ''

        If Error_Services('NoError') then
            // Confirm if row already exists. This will determine if an INSERT or UPDATE should be made.
            PrimarySQLTableName     = SQL_Services_RevDotNet('GetPrimarySQLTable', OITableName)
            JoinClause              = SQL_Services_RevDotNet('GetJoinClause', OITableName, PrimarySQLTableName, OIKeyID, OIRow)
            SQLStatement            = 'SELECT TOP 1 * FROM ' : PrimarySQLTablename : ' WHERE ' : JoinClause
            RowData                 = SQL_Services_RevDotNet('ExecuteQuery', objConnection, SQLStatement, True$)

            If RowData EQ '' then
                // Create the INSERT statements to store the data in SQL.
                TransactionQueries  = SQL_Services_RevDotNet('GetSQLInsertStatements', OITableName, OIKeyID, OIRow, OIColumns)
                
            end else
                // Create the UPDATE statements to store the data in SQL.
                TransactionQueries  = SQL_Services_RevDotNet('GetSQLUpdateStatements', OITableName, OIKeyID, OIRow, OIColumns)
            end
        end

        If Error_Services('NoError') AND (TransactionQueries NE '') then
            // Process the tranaction queries.
            SQL_Services_RevDotNet('ExecuteQueries', objConnection, TransactionQueries, True$, OITableName, OIKeyID)
        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_RevDotNet('GetMappedSQLServer', OITableName)
        objConnection       = SQL_Services_RevDotNet('GetConnectionObject', SQLServer, 2)
        TransactionQueries  = ''

        If Error_Services('NoError') then
            // Confirm if row already exists. This will determine if an INSERT or UPDATE should be made.
            PrimarySQLTableName     = SQL_Services_RevDotNet('GetPrimarySQLTable', OITableName)
            JoinClause              = SQL_Services_RevDotNet('GetJoinClause', OITableName, PrimarySQLTableName, OIKeyID, OIRow)
            SQLStatement            = 'SELECT TOP 1 * FROM ' : PrimarySQLTablename : ' WHERE ' : JoinClause
            RowData                 = SQL_Services_RevDotNet('ExecuteQuery', objConnection, SQLStatement, True$)

            If RowData NE '' then
                // Create the DELETE statements so the data in SQL is cleared out first.
                TransactionQueries  = SQL_Services_RevDotNet('GetSQLDeleteStatements', OITableName, OIKeyID)
            end
        end

        If Error_Services('NoError') AND (TransactionQueries NE '') then
            // Process the tranaction queries.
            SQL_Services_RevDotNet('ExecuteQueries', objConnection, TransactionQueries, True$)
        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_RevDotNet('GetMappedSQLServer', OITableName)
        objConnection   = SQL_Services_RevDotNet('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_RevDotNet('GetPrimarySQLTable', OITableName)
            SQLSelectStatement  = SQL_Services_RevDotNet('GetSQLSelectStatement', OITableName, SQLTableName, OIKeyID)
            SQLData             = SQL_Services_RevDotNet('ExecuteQuery', objConnection, SQLSelectStatement, True$)
            If (SQLData NE '') AND (Error_Services('NoError')) then
                OIRow   = SQL_Services_RevDotNet('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_RevDotNet('GetSecondarySQLTables', OITableName)
            If SQLTableNames NE '' then
                For Each SQLTableName in SQLTableNames using @FM
                    SQLSelectStatement  = SQL_Services_RevDotNet('GetSQLSelectStatement', OITableName, SQLTableName, OIKeyID)
                    // Create a new or get an existing Connection Object.
                    SQLServer           = SQL_Services_RevDotNet('GetMappedSQLServer', OITableName)
                    objConnection       = SQL_Services_RevDotNet('GetConnectionObject', SQLServer, 2)

                    If Error_Services('NoError') AND (SQLSelectStatement NE '') then
                        SQLData = SQL_Services_RevDotNet('ExecuteQuery', objConnection, SQLSelectStatement, True$)
                        If (SQLData NE '') AND (Error_Services('NoError')) then
                            OIRow   = SQL_Services_RevDotNet('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.
// Table    - SQL table whose row count is being requested.
//
// High level services that counts the number of rows in the designated table. This services calls other lower level
// services.
//----------------------------------------------------------------------------------------------------------------------
Service GetRowCount(Server, TableName)

    RowCount    = ''

    If (Server NE '') AND (TableName NE '') then
        // Create a new or get an existing Connection Object.
        objConnection   = SQL_Services_RevDotNet('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_RevDotNet('ExecuteQuery', objConnection, SQLCountStatement, True$)
            If Error_Services('HasError') then
                ErrorMessage    = Error_Services('GetMessage')
            end
            SQL_Services_RevDotNet('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, QueryStatement, AttemptsBeforeFailure)

    If AttemptsBeforeFailure EQ '' then AttemptsBeforeFailure = 1
    DataRows    = ''

    If ( (Server NE '') AND (QueryStatement NE '') ) then
        ErrorMessage    = ''
        objConnection   = SQL_Services_RevDotNet('GetConnectionObject', Server, 2)
        If Error_Services('NoError') then
            If Error_Services('NoError') then
                DataRows    = SQL_Services_RevDotNet('ExecuteQuery', objConnection, QueryStatement, True$, AttemptsBeforeFailure)
            end
            // Get any errors from the GetConnectionObject service or the ExecuteQuery service.
            ErrorMessage    = Error_Services('GetMessage')
        end
        SQL_Services_RevDotNet('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, 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
                //Define the dotNet version
                DotNetHandle     = StartDotNet("","4.0")
                DotNetDir        = CheckDotNet('4.0'):'\'
                SqlClientDLLPath = DotNetDir:'System.Data.dll'
                ConnectionString = SQL_Services_RevDotNet('GetConnectionString', Server)
                //Specify the DLL
                rv = Set_Property.NET(DotNetHandle, "AssemblyName", SqlClientDLLPath)
                //Define the classes
                Set_Status(0)
                objConnection = Create_Class.NET(DotNetHandle, "System.Data.SqlClient.SqlConnection", 0, ConnectionString, 'System.String')
                If Not(Get_Status(errCode)) then
                    Set_Status(0)
                    rv = Send_Message.Net(objConnection, "Open")
                    IF Not(Get_Status(errCode)) THEN
                        ConnectionState = Get_Property.Net(objConnection, "State")
                        If ConnectionState EQ 'Open' then
                            Set_Status(0)
                            objCommand = Send_Message.Net(objConnection, 'CreateCommand', '', '', 1)
                            If Not(Get_Status(errCode)) then
                                Set_Status(0)
                                objTransaction = Send_Message.Net(objConnection, 'BeginTransaction', 'Test', 'System.String', 1)
                                If Not(Get_Status(errCode)) then
                                    Set_Status(0)
                                    rv = Set_Property.NET(objCommand, 'Transaction', objTransaction)
                                    If Get_Status(errCode) then
                                        Error_Services('Add', 'Unable to set Transaction property of the SqlCommand object in the ' : Service : ' service: ' : errCode)
                                    end
                                end else
                                    Error_Services('Add', 'Unable to create the SQLTransaction object in the ' : Service : ' service: ' : errCode)
                                end
                            end else
                                Error_Services('Add', 'Unable to create the Command object in the ' : Service : ' service: ' : errCode)
                            end
                        end else
                            SQL_Services_RevDotNet('DestroyConnectionObject', objConnection)
                            objConnection   = ''
                        end 
                    end else
                        SQL_Services_RevDotNet('DestroyConnectionObject', objConnection)
                        objConnection   = ''
                    end
                end
                Attempts   += 1
            Until (objConnection NE '') OR (Attempts GE AttemptsBeforeFailure)
            Repeat
*            If objConnection and Error_Services('NoError') then
*                Memory_Services('SetValue', ServiceKeyID, objConnection)
*            end else
*                If Error_Services('NoError') then
*                    // If no error message has been set then create a generic one.
*                    Error_Services('Add', 'Unable to create the connection object to ' : Server : ' in the ' : Service : ' service: ' : Error)
*                end
*            end
        end else
            Error_Services('Add', 'Server argument was missing from the ' : Service : ' service.')
        end
*    end
    
    If objConnection and Error_Services('NoError') then
        ConnObjIndex = SRP_List('Add', ConnectionObjects@, objConnection)
        SRP_List('SetAt', CommandObjects@, ConnObjIndex, objCommand)
        SRP_List('SetAt', TransactionObjects@, ConnObjIndex, objTransaction)
    end else
        If Assigned(objConnection) then Free_Class.Net(objConnection)
        If Assigned(objCommand) then Free_Class.Net(objCommand)
        If Assigned(objTransaction) then Free_Class.Net(objTransaction)
    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
        ConnObjIndex = SRP_List('Locate', ConnectionObjects@, objConnection)
        If ConnObjIndex GT 0 then
            objCommand     = SRP_List('GetAt', CommandObjects@, ConnObjIndex)
            objTransaction = SRP_List('GetAt', TransactionObjects@, ConnObjIndex)
            Error   = ''
            // Close the connection object, but don't worry about errors. It might have already been closed.
            Set_Status(0)
            rv      = Send_Message.Net(objConnection, 'Close')
            errCode = ''
            If Not(Get_Status(errCode)) then
                Free_Class.Net(objConnection)
                Free_Class.Net(objCommand)
                Free_Class.Net(objTransaction)
                Destroyed = True$
            end else
                Error = 'Error in service ':Service:'. Failed to close SQL connection. Error code: ':errCode
            end
            If Destroyed then
                SQL_Services_RevDotNet('ClearConnectionErrors', objConnection)
*                Server  = Memory_Services('GetValue', objConnection : '*Server')
*                Memory_Services('SetValue', ServiceModule : '*GetConnectionObject*' : Server, '')
                SRP_List('RemoveAt', ConnectionObjects@, ConnObjIndex)
                SRP_List('RemoveAt', CommandObjects@, ConnObjIndex)
                SRP_List('RemoveAt', TransactionObjects@, ConnObjIndex)
            end else
                SQL_Services_RevDotNet('AddConnectionErrors', objConnection, Error)
                Error_Services('Add', 'Service : ' : Service : ', Error : ' : Error)            
            end
        end
        
    end else
        Error_Services('Add', 'The objConnection argument was missing in the ' : Service : ' service.')
    end

    Response = Destroyed

end service


//----------------------------------------------------------------------------------------------------------------------
// AddConnectionErrors
//
// objConnection    - Connection Object (see GetConnectionObject service.)
// ErrorList        - @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, ErrorList)

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

    If (objConnection NE '') AND (ErrorList NE '') then
        If ConnectionErrors NE '' then
            ConnectionErrors    := @FM : ErrorList
        end else
            ConnectionErrors     = ErrorList
        end
        Memory_Services('SetValue', ServiceKeyID, ConnectionErrors)
    end else
        Error_Services('Add', 'The objConnection or ErrorList 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_RevDotNet('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
            ConnectionString  = Database_Services('ReadDataRow', 'SERVERS', Server)
            If ConnectionString NE '' then
                ConnectionString    = Delete(ConnectionString, 2, 0, 0)
                ProviderIdx         = IndexC(ConnectionString, 'Provider', 1)
                ConnectionString    = ConnectionString[(ProviderIdx + 1), 999]
                ProviderEndIdx      = Index(ConnectionString, ';', 1)
                ConnectionString    = ConnectionString[(ProviderEndIdx + 1), 999]
            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
        Response    = ConnectionString
    end

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=BOOLEAN, AttemptsBeforeFailure)

    If AttemptsBeforeFailure EQ '' then AttemptsBeforeFailure = 1
    Results = False$    ; // Assume no results from this query for now.
    If ReturnDataRows NE True$ then ReturnDataRows = False$
    
    If (objConnection NE '') AND (QueryStatement NE '') then        
        Attempts     = 0
        ErrorMessage = ''
        ConnObjIndex = SRP_List('Locate', ConnectionObjects@, objConnection)
        If ConnObjIndex GT 0 then
            Loop
                objCommand     = SRP_List('GetAt', CommandObjects@, ConnObjIndex)
                If objCommand then
                    Set_Status(0)
                    rv = Set_Property.NET(objCommand, 'CommandText', QueryStatement)
                    If Not(Get_Status(errCode)) then
                        Set_Status(0)
                        Results = Send_Message.Net(objCommand, "ExecuteScalar")
                        If Not(Get_Status(errCode)) then
                            If ReturnDataRows EQ False$ then
                                Results = True$
                            end
                        end else
                            ErrorMessage = 'Failed to execute SQL command in the ' :Service : 'service.'
                        end
                    end
                end
                Attempts   += 1
            Until ( (ErrorMessage EQ '') OR (Attempts GE AttemptsBeforeFailure) )
            Repeat
        end else
            Error_Services('Add', 'objCommand could not be found in the ' : Service : ' service.')
        end
    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, UseTransactionControl=BOOLEAN, OITableName, OIKeyID)

    If UseTransactionControl NE True$ then UseTransactionControl = False$

    AllAccepted = True$    ; // Assume all queries were accepted for now unless otherwise proven.
    
    If (objConnection NE '') AND (QueryStatements NE '') then
        ConnObjIndex = SRP_List('Locate', ConnectionObjects@, objConnection)
        If ConnObjIndex GT 0 then
            objCommand     = SRP_List('GetAt', CommandObjects@, ConnObjIndex)
            objTransaction = SRP_List('GetAt', TransactionObjects@, ConnObjIndex)
            For Each QueryStatement in QueryStatements using @FM
                If QueryStatement NE '' then
                    Set_Status(0)
                    rv = Set_Property.Net(objCommand, 'CommandText', QueryStatement)
                    If Not(Get_Status(errCode)) then
                        Set_Status(0)
                        rv = Send_Message.Net(objCommand, 'ExecuteNonQuery')
                        If Get_Status(errCode) then
                            Error_Services('Add', 'Error executing command ':Quote(QueryStatement):' in the ' : Service : ' service.')
                        end
                    end
                end
            Until Error_Services('HasError')
            Next QueryStatement
            If UseTransactionControl EQ True$ then
                // COMMIT or ROLLBACK the transaction.
                If Error_Services('NoError') then
                    Set_Status(0)
                    rv = Send_Message.Net(objTransaction, 'Commit')
                    If Get_Status(errCode) then
                        AllAccepted = False$
                        Error_Services('Add', 'Error committing transaction in the ' : Service : ' service.')
                    end
                end else
                    AllAccepted     = False$
                    rv = Send_Message.Net(objTransaction, 'Rollback')
                end
            end
        end
        
    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.)
//
// Gets all of the data rows from the RecordSet Object. This returns an @RM array of data rows.
//----------------------------------------------------------------------------------------------------------------------
Service GetAllQueryDataRows(objRecordSet)

    DataRows    = ''

    If Len(objRecordSet) then
        BOF         = SRP_COM(objRecordSet, 'GET', 'BOF')
        EOF         = SRP_COM(objRecordSet, 'GET', 'EOF')
        If (EOF EQ False$) then
            DataRows    = SRP_COM(objRecordSet, 'CALL', 'GetString', 2, -1, @FM, @RM)
            If SRP_COM('', 'ERROR') then
                Error   = SRP_COM('', 'ERROR')
                Error_Services('Add', 'ADO error in the ' : Service : ' service: ' : SRP_COM('', 'ERROR'))
            end
            If Error_Services('NoError') then
                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
        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  = ''
    hServers = Database_Services('GetTableHandle', 'SERVERS')
    If Error_Services('NoError') then
        Select hServers
        EOF = False$
        Loop
            Readnext ServerID else EOF = True$            
        Until EOF
            Servers    := ServerID : @FM
        Repeat
        Servers[-1, 1]  = ''
        Servers = SRP_Array('SortRows', Servers, 'AL1', 'LIST', @FM, @VM)
    end

    Response = Servers

end service


//----------------------------------------------------------------------------------------------------------------------
// SaveConnectionString
//
// ServerID         - Key ID to a row in the SERVERS database table.
// ConnectionString - SQL server connection string
//
// If a row in the SERVERS table with the key ServerID exists, updates the connection string, otherwise a new row
// within the SERVERS database table containing ConnectionString.
//----------------------------------------------------------------------------------------------------------------------
Service SaveConnectionString(ServerName, ConnectionString)

    If ServerName NE '' then
        ServerRec = Database_Services('ReadDataRow', 'SERVERS', ServerName)
        ServerRec<SERVERS.CONNECTION_STRING$> = ConnectionString
        Database_Services('WriteDataRow', 'SERVERS', ServerName, ServerRec, True$, False$, True$)
    end else
        ErrorMsg = 'Error saving connection. Null server name passed in to the ' : Service : ' service.'
        Error_Services('Add', ErrorMsg)
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// CreateNewConnectionString
//
// Utilizes the Windows Data Link Properties utility to create a new connection string.
// Returns a field mark delimited array containing a suggested server name (the data source) and the connection string
// generated by the Windows Data Link Properties utility.
//----------------------------------------------------------------------------------------------------------------------
Service CreateNewConnectionString()

    Response         = ''
    ServerName       = ''
    ConnectionString = ''
    OleDBString      = ''
    Filename         = 'temp.udl'
    TempPath         = Str(\00\, 1024)
    GetTempPath(Len(TempPath), TempPath)
    Convert \00\ to '' in TempPath
    TempFilename = Str(\00\, 1024)
    GetTempFileName(TempPath, \00\, 0, TempFilename)
    OSWrite OleDBString to TempFilename
    UDLFile     = 'C:\Windows\syswow64\rundll32.exe "C:\Program Files (x86)\Common Files\System\Ole DB\oledb32.dll",OpenDSLFile ' : TempFilename
    Mode        = '-1'  ;* modal
    rv          = Utility('RUNWIN', UDLFile, Mode)       
    OSRead ConnectionString from TempFilename then
        If ConnectionString NE '' then
            Convert \FFFE\ to '' in ConnectionString
            ConnectionString = Unicode_UTF8(ConnectionString)
            DataSourcePos = Index(ConnectionString, 'Data Source', 1)
            ServerPos     = Index(ConnectionString, 'Server', 1)
            Begin Case
                Case DataSourcePos
                    ServerName = ConnectionString[(DataSourcePos + Len('Data Source') + 1), 'F;'] 
                Case ServerPos
                    ServerName = ConnectionString[(ServerPos + Len('Server') + 1), 'F;']                 
            End Case
        end
    end
    Response = ServerName : @FM : ConnectionString
    
end service


//----------------------------------------------------------------------------------------------------------------------
// EditConnection
//
// ServerID - Key ID for the SERVERS database table.
//
// Utilizes the Windows Data Link Properties utility to edit the connection string found in the SERVERS record
// associated with the ServerID key. 
//----------------------------------------------------------------------------------------------------------------------
Service EditConnection(ServerID)
    
    If ServerID NE '' then
        ServerRec = Database_Services('ReadDataRow', 'SERVERS', ServerID)
        If Error_Services('NoError') then
            ConnectionString = ServerRec<SERVERS.CONNECTION_STRING$>
            TempPath      = Str(\00\, 1024)
            GetTempPath(Len(TempPath), TempPath)
            TempFilename  = Str(\00\, 1024)
            GetTempFileName(TempPath, \00\, 0, TempFilename)
            If ConnectionString NE '' then
                UnicodeString = \FFFE\ : Str_Unicode(ConnectionString)
                OSWrite UnicodeString To TempFilename
            end else
                OSWrite '' to TempFileName
            end
            Program = 'C:\Windows\syswow64\rundll32.exe "C:\Program Files (x86)\Common Files\System\Ole DB\oledb32.dll",OpenDSLFile ':TempFilename
            Mode    = '-1'  ;* modal
            rv      = Utility("RUNWIN", Program, Mode)
            NewFile = ''
            OSRead NewConnectionString from TempFilename then           
                Convert \FFFE\ to '' in NewConnectionString
                NewConnectionString = Unicode_UTF8(NewConnectionString)
                ServerRec<SERVERS.CONNECTION_STRING$> = NewConnectionString
                Database_Services('WriteDataRow', 'SERVERS', ServerID, ServerRec, True$, False$, True$)
            end
        end
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// DeleteConnection
//
// ServerID - Key ID for the SERVERS database table.
//
// Deletes the SERVERS record associated with the ServerID key.
//----------------------------------------------------------------------------------------------------------------------
Service DeleteConnection(ServerID)
    
    If ServerID NE '' then
        Database_Services('DeleteDataRow', 'SERVERS', ServerID, True$)
    end
    
end service


//----------------------------------------------------------------------------------------------------------------------
// SetConnectionDesc
//
// ServerID     - Key ID for the SERVERS database table.
// Description  - Description of the server connection
//
// Sets the DESCRIPTION column of the SERVERS record associated with the ServerID key.
//----------------------------------------------------------------------------------------------------------------------
Service SetConnectionDesc(ServerID, Description)
    
    If ServerID NE '' then
        ServerRec = Database_Services('ReadDataRow', 'SERVERS', ServerID)
        ServerRec<SERVERS.DESCRIPTION$> = Description
        Database_Services('WriteDataRow', 'SERVERS', ServerID, ServerRec)
    end
    
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)

    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_RevDotNet('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


//----------------------------------------------------------------------------------------------------------------------
// GetMappedSQLServer
//
// Returns the name of the SQL Server that the indicated OpenInsight table is mapped to.
//----------------------------------------------------------------------------------------------------------------------
Service GetMappedSQLServer(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_RevDotNet('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', 'OITableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = SQLServer

end service


//----------------------------------------------------------------------------------------------------------------------
// GetDatabases
//
// objConnection    - Connection Object (see GetConnectionObject service.) - [Optional, but Server is then Required]
// Server           - Name of a defined server. - [Optional, but objConnection is then Required]
//
// Returns an @FM list of databases available through the indicated connection object. If the connection object is
// empty, then a new one will be created (and then subsequently destroyed) for the indicated server.
//----------------------------------------------------------------------------------------------------------------------
Service GetDatabases(objConnection, Server)

    Databases   = ''

    If (objConnection NE '') OR (Server NE '') then
        If objConnection EQ '' then
            objConnection   = SQL_Services_RevDotNet('GetConnectionObject', Server, 3)
            NewObject       = True$
        end else
            NewObject   = False$
        end

        If Error_Services('NoError') then
            SQLQuery    = "SELECT name FROM sys.databases WHERE name NOT IN ('master', 'tempdb', 'model', 'msdb')"
            Databases   = SQL_Services_RevDotNet('ExecuteQuery', objConnection, SQLQuery, True$)
            If Databases EQ 0 then
                Databases = ''
                Error_Services('Add', 'Unknown error in the ' : Service : ' service for server ' : Server : '.')
            end else
                Convert @RM to @FM in Databases
            end
        end

        If NewObject EQ True$ then
            SQL_Services_RevDotNet('DestroyConnectionObject', objConnection)
        end
    end else
        Error_Services('Add', 'objConnection and Server arguments were both missing in the ' : Service : ' service.')
    end

    Response = Databases

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSchemas
//
// objConnection    - Connection Object (see GetConnectionObject service.) - [Optional, but Server is then Required]
// Server           - Name of a defined server. - [Optional, but objConnection is then Required]
// Database         - Name of a specific database to use. If empty, then all databases will be used. - [Required]
//
// Returns an @FM list of schemas available for the indicated database. If the connection object is empty, then a new
// one will be created (and then subsequently destroyed) for the indicated server.
//----------------------------------------------------------------------------------------------------------------------
Service GetSchemas(objConnection, Server, Database)

    Schemas     = ''

    If Database NE '' then
        If (objConnection NE '') OR (Server NE '') then
            If objConnection EQ '' then
                objConnection   = SQL_Services_RevDotNet('GetConnectionObject', Server, 3)
                NewObject       = True$
            end else
                NewObject   = False$
            end

            If Error_Services('NoError') then
                SQLQuery    = "SELECT DISTINCT TABLE_SCHEMA FROM [" : Database : "].INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE='BASE TABLE'"
                Schemas     = SQL_Services_RevDotNet('ExecuteQuery', objConnection, SQLQuery, True$)
                If Error_Services('NoError') then
                    Convert @RM to @FM in Schemas
                end else
                    Message     = Error_Services('GetMessage')
                    If IndexC(Message, 'not able to access the database', 1) AND IndexC(Message, 'under the current security context', 1) then
                        // Users does not have permission. Clear the error rather than report it.
                        Error_Services('Clear')
                    end
                end
            end

            If NewObject EQ True$ then
                ErrorMessage    = Error_Services('GetMessage')
                SQL_Services_RevDotNet('DestroyConnectionObject', objConnection)
                If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)
            end
        end else
            Error_Services('Add', 'objConnection and Server arguments were both missing in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'Database argument was missing in the ' : Service : ' service.')
    end

    Response    = Schemas

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTables
//
// objConnection        - Connection Object (see GetConnectionObject service.) - [Optional, but Server is then Required]
// Server               - Name of a defined server. - [Optional, but objConnection is then Required]
// Databases            - @FM delimited list of databases to use. If empty, then all databases will be used.
//                        - [Optional]
// Schemas              - @FM delimited list of schemas to use. If empty, then all schemas will be used. If Databases is
//                        empty, then Schemas will be ignored. - [Optional]
// ReturnFullObjects    - Boolean flag to indicate if full object notation (e.g., Server.Database.Schema.Table) should
//                        be returned or just the tables. Default is False$ - [Optional]
// BracketObjects       - Boolean flag to indicate if the objects being returned should be bracketed in case some of the
//                        object names are reserved words. Default is False$ - [Optional]
//
// Returns an @FM list of tables using full object name formatting (i.e., Database.Schema.Table) available for the
// indicated connection object (or server). If the connection object is empty, then a new one will be created (and then
// subsequently destroyed) for the indicated server.
//----------------------------------------------------------------------------------------------------------------------
Service GetTables(objConnection, Server, Databases, Schemas, IncludeServerObject=BOOLEAN, IncludeDatabaseObject=BOOLEAN, IncludeSchemaObject=BOOLEAN, BracketObjects=BOOLEAN)

    Tables  = ''

    If IncludeServerObject NE True$     then IncludeServerObject    = False$
    If IncludeDatabaseObject NE True$   then IncludeDatabaseObject  = False$
    If IncludeSchemaObject NE True$     then IncludeSchemaObject    = False$
    If BracketObjects NE True$          then BracketObjects         = False$
    If Databases EQ ''                  then Schemas                = ''

    If (objConnection NE '') OR (Server NE '') then
        If objConnection EQ '' then
            objConnection   = SQL_Services_RevDotNet('GetConnectionObject', Server, 3)
            NewObject       = True$
        end else
            NewObject   = False$
        end

        If Error_Services('NoError') then
            If Databases EQ '' then Databases = SQL_Services_RevDotNet('GetDatabases', objConnection, Server)
            If Error_Services('NoError') AND (Databases NE '') then
                For Each Database in Databases using @FM
                    If Schemas EQ '' then Schemas = SQL_Services_RevDotNet('GetSchemas', objConnection, Server, Database)
                    If Error_Services('NoError') AND (Schemas NE '') then
                        For Each Schema in Schemas using @FM
                            SQLQuery    = "SELECT TABLE_NAME FROM [" : Database : "].INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='" : Schema : "'"
                            TableNames  = SQL_Services_RevDotNet('ExecuteQuery', objConnection, SQLQuery, True$)
                            Convert @RM to @FM in TableNames
                            If Error_Services('NoError') AND (TableNames NE '') then
                                If (IncludeServerObject EQ True$) OR (IncludeDatabaseObject EQ True$) OR (IncludeSchemaObject EQ True$) then
                                    For Each Table in TableNames using @FM
                                        TableObject = Table
                                        If IncludeSchemaObject EQ True$ then
                                            TableObject = Schema : '.' : TableObject
                                            If IncludeDatabaseObject EQ True$ then
                                                TableObject = Database : '.' : TableObject
                                                If IncludeServerObject EQ True$ then
                                                    TableObject = Server : '.' : TableObject
                                                end
                                            end
                                        end
                                        Tables := TableObject : @FM
                                    Next Table
                                end else
                                    Tables := TableNames : @FM
                                end
                            end
                        Next Schema
                        Schemas = ''
                    end
                Next Database
            end
            Tables[-1, 1]  = ''
            If (Tables NE '') AND (BracketObjects EQ True$) then
                Swap '.' with '].[' in Tables
                Swap @FM with ']' : @FM : '[' in Tables
                Tables = '[' : Tables : ']'
                Tables = SRP_Array('SortRows', Tables, 'AL1', 'LIST', @FM, @VM)
            end
        end

        If NewObject EQ True$ then
            ErrorMessage    = Error_Services('GetMessage')
            SQL_Services_RevDotNet('DestroyConnectionObject', objConnection)
            If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)
        end
    end else
        Error_Services('Add', 'objConnection and Server arguments were both missing in the ' : Service : ' service.')
    end

    Response    = Tables

end service


//----------------------------------------------------------------------------------------------------------------------
// GetTableColumns
//
// objConnection    - Connection Object (see GetConnectionObject service.) - [Optional, but Server is then Required]
// Server           - Name of a defined server. - [Optional, but objConnection is then Required]
// Database         - Database object for the table. - [Optional]
// Schema           - Schema object for the table. - [Optional]
// Table            - Database Table whose column data is being requested. The Table argument might have the Server,
//                    Database, and/or Schema objects embedded. If so, then these will override any values specified
//                    in the Server, Database, or Schema arguments. - [Required]
//
// Returns an @FM/@VM array of database column information.
//----------------------------------------------------------------------------------------------------------------------
Service GetTableColumns(objConnection, Server, Database, Schema, Table)

    TableColumns    = ''

    If Table NE '' then
        Convert '[]' to '' in Table ; // Remove any brackets so the query won't fail.
        If Index(Table, '.', 1) then
            // Table came formatted with other objects. Parse these out.
            NumObjects  = DCount(Table, '.')
            Begin Case
                Case NumObjects EQ 4
                    Server      = Table[1, '.']
                    Database    = Table[Col2() + 1, '.']
                    Schema      = Table[Col2() + 1, '.']
                    Table       = Table[Col2() + 1, '.']
                Case NumObjects EQ 3
                    Database    = Table[1, '.']
                    Schema      = Table[Col2() + 1, '.']
                    Table       = Table[Col2() + 1, '.']
                Case NumObjects EQ 2
                    Schema      = Table[1, '.']
                    Table       = Table[Col2() + 1, '.']
            End Case
        end

        If (objConnection NE '') OR (Server NE '') then
            If objConnection EQ '' then
                objConnection   = SQL_Services_RevDotNet('GetConnectionObject', Server, 3)
                NewObject       = True$
            end else
                NewObject   = False$
            end

            If Error_Services('NoError') then
                ErrorMessage    = ''
                
                If Database NE '' then
                    SQL_Services_RevDotNet('ExecuteQuery', objConnection, "USE " : Database, False$)
                end
                If Error_Services('NoError') then
                    QueryStatement  = ""
                    QueryStatement := "SELECT "
                    QueryStatement := "    c.name 'Column Name', "
                    QueryStatement := "    t.Name 'Data type', "
                    QueryStatement := "    c.max_length 'Max Length', "
                    QueryStatement := "    c.is_nullable, "
                    QueryStatement := "    ISNULL(i.is_primary_key, 0) 'Primary Key' "
                    QueryStatement := "FROM    "
                    QueryStatement := "    sys.columns c "
                    QueryStatement := "INNER JOIN "
                    QueryStatement := "    sys.types t ON c.user_type_id = t.user_type_id "
                    QueryStatement := "LEFT OUTER JOIN "
                    QueryStatement := "    sys.index_columns ic ON ic.object_id = c.object_id AND ic.column_id = c.column_id "
                    QueryStatement := "LEFT OUTER JOIN "
                    QueryStatement := "    sys.indexes i ON ic.object_id = i.object_id AND ic.index_id = i.index_id "
                    QueryStatement := "WHERE"
                    QueryStatement := "    c.object_id = OBJECT_ID('"
                    If Database NE '' then QueryStatement := "[" : Database : "]."
                    If Schema NE '' then QueryStatement := "[" : Schema : "]."
                    QueryStatement := "[" : Table : "]')"

                    TableColumns    = SQL_Services_RevDotNet('ExecuteQuery', objConnection, QueryStatement, True$)
                    If Error_Services('NoError') then
                        Convert @FM to @VM in TableColumns
                        Convert @RM to @FM in TableColumns
                        TableColumns    = SRP_Array('SortRows', TableColumns, 'DR5' : @FM : 'AL1', 'LIST', @FM, @VM)
                    end else
                        ErrorMessage    = Error_Services('GetMessage')
                    end
                    SQL_Services_RevDotNet('DestroyConnectionObject', objConnection)
                    If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)
                end
            end

            If NewObject EQ True$ then
                ErrorMessage    = Error_Services('GetMessage')
                SQL_Services_RevDotNet('DestroyConnectionObject', objConnection)
                If ErrorMessage NE '' then Error_Services('Add', ErrorMessage)
            end
        end else
            Error_Services('Add', 'The Server or Table argument was missing in the ' : Service : ' service.')
        end
    end else
        Error_Services('Add', 'Table argument was missing in the ' : Service : ' service.')
    end

    Response = TableColumns

end service


//----------------------------------------------------------------------------------------------------------------------
// GetPrimarySQLTable
//
// OITableName  - The name of the OpenInsight table that is mapped to the primary SQL table. - [Required]
//
// 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_RevDotNet('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
//
// OITableName  - The name of the OpenInsight table that is mapped to secondary SQL tables. - [Required]
//
// 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_RevDotNet('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
//
// OITableName  - The name of the OpenInsight table that columns mapped to a SQL table with static values. - [Required]
//
// 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)

    If StaticSQLColumnNames EQ '' then
        If (OITableName NE '') then
            // Add all OpenInsight columns defined in the SQL Join Clause.
            OITableToSQLMap = SQL_Services_RevDotNet('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 : ']')
                    StaticSQLColumnNames   := SRP_JSON(objStaticColumn, 'GETVALUE', 'SQLColumnName') : @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
//
// OITableName  - The name of the OpenInsight table that columns mapped to a SQL table with static values. - [Required]
//
// 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_RevDotNet('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 : ']')
                    StaticValue             = SRP_JSON(objStaticColumn, 'GETVALUE', 'StaticValue')
                    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
//
// OITableName  - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// SQLTableName - The name of the SQL table that the Openinsight table is mapped to. - [Required]
//
// 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

    ServiceKeyID  := '*' : OITableName : '*' : SQLTableName
    AMVMaster      = Memory_Services('GetValue', ServiceKeyID)

    If AMVMaster EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            OITableToSQLMap = SQL_Services_RevDotNet('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.
                    TableArray = SRP_JSON(MapObj, 'Get', 'SecondarySQLTables')
                    TableObjs  = SRP_JSON(TableArray, 'GetElements')
                    TableFound = False$
                    For Each TableObj in TableObjs using @FM
                        SecondarySQLTableName   = SRP_JSON(TableObj, 'GETVALUE', 'Name', '')
                        If SQLTableName _EQC SecondarySQLTableName then
                            JoinClassArray = SRP_JSON(TableObj, 'Get', 'JoinClause')
                            JoinClassObjs  = SRP_JSON(JoinClassArray, 'GetElements')
                            For Each JoinClassObj in JoinClassObjs using @FM
                                If SRP_JSON(JoinClassObj, 'GetValue', 'AMVMaster') EQ True$ then
                                    AMVMaster     = SRP_JSON(JoinClassObj, 'GetValue', 'OIColumnName')
                                end
                                SRP_JSON(JoinClassObj, 'Release')
                            Until AMVMaster NE ''
                            Next JoinClassObj
                            TableFound = True$
                            SRP_JSON(JoinClassArray, 'Release')
                        end
                        SRP_JSON(TableObj, 'Release')
                    Until TableFound
                    Next TableObj
                    SRP_JSON(TableArray, 'Release')
                end
                SRP_JSON(MapObj, 'Release')
            end

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

    Response    = AMVMaster

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedJoinedOIColumnNames
//
// OITableName      - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// SQLTableName     - The name of the SQL table that the Openinsight table is mapped to. - [Required]
// ValuesReturned   - A return varabiable that will contain the type of values each associated column should return.
//                    See below for more information. - [Optional]
//
// Returns an @FM list of column names from the indicated OpenInsight table that are used to join with the indicated
// SQL table. This service will also populate the ValuesReturned variable with the intended value to be returned by the
// associated column (.e.g, Column Data or Index Position).
//----------------------------------------------------------------------------------------------------------------------
Service GetMappedJoinedOIColumnNames(OITableName, SQLTableName, Ref ValuesReturned)

    Convert @Lower_Case to @Upper_Case in OITableName

    ServiceKeyID   := '*' : OITableName : '*' : SQLTableName
    ColumnNames     = Memory_Services('GetValue', ServiceKeyID)
    ValuesReturned  = Memory_Services('GetValue', ServiceKeyID : '*ValuesReturned')

    If ColumnNames EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            OITableToSQLMap = SQL_Services_RevDotNet('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', '')
                    ValueReturned   = SRP_JSON(JoinArray, 'GETVALUE', '[' : ColumnNo : ']' : 'ValueReturned', '')
                    ColumnNames    := OIColumnName : @FM
                    ValuesReturned := ValueReturned : @FM
                Next ColumnNo
                ColumnNames[-1, 1]  = ''
                ValuesReturned[-1, 1]   = ''
                SRP_JSON(JoinArray, 'RELEASE')
            end

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

    Response    = ColumnNames

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedJoinedSQLColumnNames
//
// OITableName      - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// SQLTableName     - The name of the SQL table that the Openinsight table is mapped to. - [Required]
//
// Returns an @FM list of column names from the indicated SQL table that are used to join with the indicated OpenInsight
// table.
//----------------------------------------------------------------------------------------------------------------------
Service GetMappedJoinedSQLColumnNames(OITableName, SQLTableName)

    Convert @Lower_Case to @Upper_Case in OITableName

    ServiceKeyID   := '*' : OITableName : '*' : SQLTableName
    ColumnNames     = Memory_Services('GetValue', ServiceKeyID)

    If ColumnNames EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            OITableToSQLMap = SQL_Services_RevDotNet('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
                    SQLColumnName   = SRP_JSON(JoinArray, 'GETVALUE', '[' : ColumnNo : ']' : 'SQLColumnName', '')
                    ColumnNames    := SQLColumnName : @FM
                Next ColumnNo
                ColumnNames[-1, 1]  = ''
                SRP_JSON(JoinArray, 'RELEASE')
            end

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

    Response    = ColumnNames

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedJoinedOperators
//
// OITableName      - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// SQLTableName     - The name of the SQL table that the Openinsight table is mapped to. - [Required]
//
// Returns an @FM list of operators from the indicated SQL table that are used to join with the indicated OpenInsight
// table.
//----------------------------------------------------------------------------------------------------------------------
Service GetMappedJoinedOperators(OITableName, SQLTableName)

    Convert @Lower_Case to @Upper_Case in OITableName

    ServiceKeyID   := '*' : OITableName : '*' : SQLTableName
    Operators       = Memory_Services('GetValue', ServiceKeyID)

    If Operators EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            OITableToSQLMap = SQL_Services_RevDotNet('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
                    Operator    = SRP_JSON(JoinArray, 'GETVALUE', '[' : ColumnNo : ']' : 'Operator', '')
                    Operators  := Operator : @FM
                Next ColumnNo
                Operators[-1, 1]    = ''
                SRP_JSON(JoinArray, 'RELEASE')
            end

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

    Response    = Operators

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedJoinedSQLColumnValues
//
// OITableName  - The name of the OpenInsight table that is mapped to the SQL table to. - [Required]
// SQLTableName - The name of the SQL table that the Openinsight table is mapped to. - [Required]
// OIKeyID      - The Key ID to the OpenInsight row being written to SQL. - [Required]
// OIRow        - The OpenInsight row being written to SQL. - [Optional]
// AMVPosition  - The position within the multivalue column that should be used in the join clause if the OpenInsight
//                column is supposed to use its Index Position. - [Optional]
//
// Returns an @FM delimited list of SQL column values for the indicated table that are derived from the indicated
// OpenInsight table and associated joined columns. This service is normally called to create a fully formed Insert
// query statement. Note: OIRow will likely be an empty string since most join clauses will be based on the OpenInsight
// Key ID.
//----------------------------------------------------------------------------------------------------------------------
Service GetMappedJoinedSQLColumnValues(OITableName, SQLTableName, OIKeyID, OIRow, AMVPosition)

    Convert @Lower_Case to @Upper_Case in OITableName

    If Num(AMVPosition) else AMVPosition = ''

    ColumnValues    = ''

    If (OITableName NE '') AND (SQLTableName NE '') AND (OIKeyID NE '') then
        FieldArray      = Database_Services('ReadDataRow', 'DICT.' : OITablename, '%FIELDS%')
        OIColumnNames   = SQL_Services_RevDotNet('GetMappedJoinedOIColumnNames', OITableName, SQLTableName, ValuesReturned)
        If Error_Services('NoError') then
            If OIColumnNames NE '' then
                For Each OIColumnName in OIColumnNames using @FM setting fPos
                    ValueReturned = ValuesReturned<fPos>
                    If ValueReturned EQ '' then ValueReturned = 'Column Data'
                    Locate OIColumnName in FieldArray<FIELDS_NAME$> using @VM setting vPos then
                        OIColumnToSQLMap    = SQL_Services_RevDotNet('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>
                                IsOIKey         = False$    ; // Assume false for now.
                                If ValueReturned EQ 'Column Data' then
                                    If OIColumnType EQ 'F' then
                                        OIColumnPos = FieldArray<FIELDS_FIELD_NO$, vPos>
                                        If OIColumnPos GT 0 then
                                            OIColumnValue   = OIRow<OIColumnPos>
                                        end else
                                            IsOIKey         = True$
                                            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 '') AND (IsOIKey NE True$) then
                                        OIColumnValue   = OIColumnValue<0, AMVPosition>
                                    end
                                end else
                                    If AMVPosition EQ '' then AMVPosition = 1
                                    OIColumnValue   = AMVPosition
                                end
                                If ValueReturned EQ 'Column Data' then
                                    // Make applicable data conversions.
                                    Begin Case
                                        Case OIColumnValue EQ ''
                                            // Empty strings are treated as NULL values.
                                            OIColumnValue   = 'NULL'
                                        Case InList('varchar,nvarchar,char,nchar', SQLColumnType, ',')
                                            // Single quote the value.
                                            SQLColumnLength = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnLength')
                                            OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                            If OIColumnConv NE '' then
                                                OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                            end
                                            If (SQLColumnLength EQ '') OR (SQLColumnLength EQ '-1') then SQLColumnLength = Len(OIColumnValue)
                                            OIColumnValue   = OIColumnValue[1, SQLColumnLength, 1]
                                            Swap "'" with "''" in OIColumnValue
                                            OIColumnValue   = "'" : OIColumnValue : "'"
                                        Case InList('tinyint,smallint,bigint,int', SQLColumnType, ',')
                                            // Treat as an integer.
                                            OIColumnConv    = 'MD0'
                                            If OIColumnConv NE '' then
                                                OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                            end
                                        Case InList('decimal,numeric,float,real', SQLColumnType, ',')
                                            // Treat as a floating point number using any OI column output conversion if it
                                            // exists.
                                            OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                            Convert 'Z' to '' in OIColumnConv   ; // No empty strings with numeric columns.
                                            If OIColumnConv NE '' then
                                                OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                            end
                                        Case SQLColumnType _EQC 'bit'
                                            // Treat as a Boolean, but only return a 1, 0, or NULL.
                                            If OIColumnValue EQ '' then
                                                OIColumnValue = 'NULL'
                                            end else
                                                If OIColumnValue NE False$ then OIColumnValue = True$
                                            end
                                        Case SQLColumnType _EQC 'date'
                                            OIColumnValue   = "'" : (SRP_Date('Format', OIColumnValue, 'YYYY-MM-DD')) : "'"
                                        Case SQLColumnType _EQC 'datetime'
                                            OIColumnValue   = "'" : (SRP_DateTime('Format', OIColumnValue, 'YYYY-MM-DD hh:mm:ss')) : "'"
                                        Case SQLColumnType _EQC 'time'
                                            OIColumnValue   = "'" : (SRP_Time('Format', OIColumnValue, 'hh:mm:ss')) : "'"
                                        Case Otherwise$
                                            // Treat as a character type.
                                            SQLColumnLength = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnLength')
                                            OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                            If OIColumnConv NE '' then
                                                OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                            end
                                            If (SQLColumnLength EQ '') OR (SQLColumnLength EQ '-1') then SQLColumnLength = Len(OIColumnValue)
                                            OIColumnValue   = OIColumnValue[1, SQLColumnLength, 1]
                                            Swap "'" with "''" in OIColumnValue
                                            OIColumnValue   = "'" : OIColumnValue : "'"
                                    End Case
                                end
                                // 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
                                ColumnValues   := OIColumnValue : @FM

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

    Response    = ColumnValues

end service


//----------------------------------------------------------------------------------------------------------------------
// GetJoinClause
//
// OITableName      - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// SQLTableName     - The name of the SQL table that the Openinsight table is mapped to. - [Required]
// OIKeyID          - The Key ID to the OpenInsight row being written to SQL. - [Required]
// OIRow            - The OpenInsight row being written to SQL. - [Optional]
// AMVPosition      - The position within the multivalue column that should be used in the join clause if the
//                    OpenInsight column is supposed to use its Index Position. - [Optional]
// IgnoreAMVMaster  - Boolean falg to indicate if the joined column associated with the OpenInsight AMV Master should
//                    be ignored. This is necessary for Join Clauses used for Select and Delete statements. - [Optional]
//
// 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, AMVPosition, IgnoreAMVMaster=BOOLEAN)

    Convert @Lower_Case to @Upper_Case in OITableName

    JoinClause  = ''

    If IgnoreAMVMaster NE True$ then IgnoreAMVMaster = False$

    If (OITableName NE '') AND (SQLTableName NE '') AND (OIKeyID NE '') then
        JoinedSQLColumns    = SQL_Services_RevDotNet('GetMappedJoinedSQLColumnNames', OITableName, SQLTableName)
        If JoinedSQLColumns NE '' then
            JoinedOperators = SQL_Services_RevDotNet('GetMappedJoinedOperators', OITableName, SQLTableName)
            JoinedSQLValues = SQL_Services_RevDotNet('GetMappedJoinedSQLColumnValues', OITableName, SQLTableName, OIKeyID, OIRow, AMVPosition)
            If IgnoreAMVMaster EQ True$ then
                // Get all of the OpenInsight columns associated with this join and the AMV Master. Remove the SQL column,
                // operator, and value associated with the AMV Master.
                AMVMaster   = SQL_Services_RevDotNet('GetAMVMaster', OITableName, SQLTableName)
                If AMVMaster NE '' then
                    JoinedOIColumns = SQL_Services_RevDotNet('GetMappedJoinedOIColumnNames', OITableName, SQLTableName)
                    Locate AMVMaster in JoinedOIColumns using @FM setting Pos then
                        JoinedSQLColumns    = Delete(JoinedSQLColumns, Pos, 0, 0)
                        JoinedOperators     = Delete(JoinedOperators, Pos, 0, 0)
                        JoinedSQLValues     = Delete(JoinedSQLValues, Pos, 0, 0)
                    end
                end
            end
            For Each JoinedColumn in JoinedSQLColumns using @FM setting Pos
                JoinClause := JoinedColumn : ' '
                JoinClause := JoinedOperators<Pos> : ' '
                JoinClause := JoinedSQLValues<Pos>
                JoinClause := @FM
            Next JoinedColumn
            JoinClause[-1, 1]   = ''
            Swap @FM with ' AND ' in JoinClause
        end
    end else
        Error_Services('Add', 'The OITableName, SQLTableName, OIKeyID argument was missing in the ' : Service : ' service.')
    end

    Response    = JoinClause

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedOIColumnNames
//
// OITableName              - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// SQLTableName             - The name of the SQL table that the Openinsight table is mapped to. - [Required]
// ExcludeJoinedColumns     - Boolean flag indicating if the columns used for the join should be excluded from the
//                            list of columns returned. This would be done if the columns are used to insert or update
//                            the SQL table. Default is true. - [Optional]
//
// 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, ExcludeJoinedColumns=BOOLEAN)

    Convert @Lower_Case to @Upper_Case in OITableName
    If ExcludeJoinedColumns NE False$ then ExcludeJoinedColumns = True$

    ServiceKeyID := '*' : OITableName : '*' : SQLTableName : '*' : ExcludeJoinedColumns
    ColumnNames   = Memory_Services('GetValue', ServiceKeyID)

    If ColumnNames EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            // 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
            // GetMappedJoinedOIColumnNames service. These will be skipped.
            AllOIColumnToSQLMaps    = SQL_Services_RevDotNet('GetAllOIColumnToSQLMaps', OITableName)
            For Each OIColumnToSQLMapList in AllOIColumnToSQLMaps using @FM setting fPos
                (OIColumnName, OIColumnToSQLMap) using @VM = OIColumnToSQLMapList
                If SRP_JSON(MapObj, 'PARSE', OIColumnToSQLMap) EQ '' then
                    MappedSQLTableName  = SRP_JSON(MapObj, 'GETVALUE', 'SQLTableName')
                    MappedSQLColumnName = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnName')
                    If (MappedSQLTableName NE '') AND (MappedSQLColumnName NE '') AND (SQLTableName EQ MappedSQLTableName) then
                        Locate OIColumnName in ColumnNames using @FM setting fPos else
                            ColumnNames := OIColumnName : @FM
                        end
                    end
                    SRP_JSON(MapObj, 'Release')
                end
            Next OIColumnToSQLMap

            If ExcludeJoinedColumns EQ True$ then
                // Get all OpenInsight columns defined in the SQL Join Clause. Then remove any that are rely upon Column
                // Data mapping rather than Index Position. This will avoid duplicate columns being used in a SQL statement.
                JoinedValuesReturned = ''
                JoinedOIColumns = SQL_Services_RevDotNet('GetMappedJoinedOIColumnNames', OITableName, SQLTableName, JoinedValuesReturned)
                If JoinedOIColumns NE '' then
                    For Each JoinedColumnName in JoinedOIColumns using @FM Setting JoinedPos
                        Locate JoinedColumnName in ColumnNames using @FM setting Pos then
                            If JoinedValuesReturned<JoinedPos> EQ 'Column Data' then
                                ColumnNames = Delete(ColumnNames, Pos, 0, 0)
                            end
                        end
                    Next JoinedColumnName
                end
            end

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

    Response    = ColumnNames

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedOIColumnValues
//
// OITableName  - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// SQLTableName - The name of the SQL table that the Openinsight table is mapped to. - [Required]
// SQLRow       - The SQL row that will be mapped to the OpenInsight row. - [Required]
// OIRow        - The OpenInsight row with pre-existing data. See below for more information. - [Optional]
//
// 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

    If (OITableName NE '') AND (SQLTableName NE '') AND (SQLData NE '') then
        MappedOIColumnNames     = SQL_Services_RevDotNet('GetMappedOIColumnNames', OITableName, SQLTableName, False$)
        MappedSQLColumnNames    = SQL_Services_RevDotNet('GetMappedSQLColumnNames', OITableName, SQLTableName, '', False$)
        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_RevDotNet('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 InList('varchar,nvarchar,char,nchar', SQLColumnType, ',')
                                            // Single quote the value.
                                            OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                            If OIColumnConv NE '' then
                                                OIColumnValue   = Iconv(OIColumnValue, OIColumnConv)
                                            end
                                        Case InList('tinyint,smallint,bigint,int', SQLColumnType, ',')
                                            // Treat as an integer.
                                            OIColumnConv    = 'MD0'
                                            If OIColumnConv NE '' then
                                                OIColumnValue   = Iconv(OIColumnValue, OIColumnConv)
                                            end
                                        Case InList('decimal,numeric,float,real', SQLColumnType, ',')
                                            // Treat as a floating point number using any 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'
                                            OIColumnValue   = SRP_Date('Parse', OIColumnValue, 'MM/DD/YYYY')
                                        Case SQLColumnType _EQC 'datetime'
                                            OIColumnValue   = SRP_DateTime('Parse', OIColumnValue, 'MM/DD/YYYY hh:mm:ss')
                                        Case SQLColumnType _EQC 'time'
                                            OIColumnValue   = SRP_Time('Parse', OIColumnValue, 'hh:mm:ss')
                                        Case Otherwise$
                                            // Treat as a character type.
                                            OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                            If OIColumnConv NE '' then
                                                OIColumnValue   = Iconv(OIColumnValue, OIColumnConv)
                                            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


//----------------------------------------------------------------------------------------------------------------------
// GetMappedSQLColumnNames
//
// OITableName              - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// SQLTableName             - The name of the SQL table that the Openinsight table is mapped to. - [Required]
// OIColumns                - An @FM delimited list of OpenInsight table columns that should only be used to write to
//                            SQL. If empty, all columns that have a map defintion will be used. - [Optional]
// ExcludeJoinedColumns     - Boolean flag indicating if the columns used for the join should be excluded from the
//                            list of columns returned. This would be done if the columns are used to insert or update
//                            the SQL table. Default is true. - [Optional]
//
// 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, ExcludeJoinedColumns)

    Convert @Lower_Case to @Upper_Case in OITableName
    If ExcludeJoinedColumns NE False$ then ExcludeJoinedColumns = True$
    ServiceKeyID := '*' : OITableName : '*' : SQLTableName : '*' : ExcludeJoinedColumns
    ColumnNames   = Memory_Services('GetValue', ServiceKeyID)

    If ColumnNames EQ '' then
        If (OITableName NE '') AND (SQLTableName NE '') then
            If OIColumns NE '' then
                Transfer OIColumns to OIColumnNames
            end else
                // The GetMappedOIColumnNames service will automatically remove any columns that are also mapped in
                // a join clause with the "Column Data" return value attribute.
                OIColumnNames   = SQL_Services_RevDotNet('GetMappedOIColumnNames', OITableName, SQLTableName, ExcludeJoinedColumns)
            end
            If OIColumnNames NE '' then
                For Each OIColumnName in OIColumnNames using @FM setting fPos
                    OIColumnToSQLMap    = SQL_Services_RevDotNet('GetOIColumnToSQLMap', OITableName, OIColumnName)
                    If Error_Services('NoError') then
                        If SRP_JSON(MapObj, 'PARSE', OIColumnToSQLMap) EQ '' then
                            SQLColumnName   = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnName')
                            If SQLColumnName NE '' then
                                ColumnNames    := SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnName') : @FM
                            end
                            SRP_JSON(MapObj, 'RELEASE')
                        end
                    end
                Next OIColumnName
            end
            ColumnNames[-1, 1]  = ''
            If ColumnNames NE '' then Memory_Services('SetValue', ServiceKeyID, ColumnNames)
        end else
            Error_Services('Add', 'The OITableName or SQLTableName argument was missing in the ' : Service : ' service.')
        end
    end

    Response    = ColumnNames

end service


//----------------------------------------------------------------------------------------------------------------------
// GetMappedSQLColumnValues
//
// OITableName  - The name of the OpenInsight table that is mapped to the SQL table to. - [Required]
// SQLTableName - The name of the SQL table that the Openinsight table is mapped to. - [Required]
// OIKeyID      - The Key ID to the OpenInsight row being written to SQL. - [Required]
// OIRow        - The OpenInsight row being written to SQL. - [Required]
// AMVPosition  - The position within the multivalue column that should be used in the join clause if the OpenInsight
//                column is supposed to use its Index Position. - [Optional]
// OIColumns    - An @FM delimited list of OpenInsight table columns that should only be used to write to SQL. If empty,
//                all columns that have a map defintion will be used. - [Optional]
//
// 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

    If Num(AMVPosition) else AMVPosition = ''

    ColumnValues    = ''

    If (OITableName NE '') AND (SQLTableName NE '') AND (OIKeyID NE '') AND (OIRow NE '') then
        FieldArray      = Database_Services('ReadDataRow', 'DICT.' : OITablename, '%FIELDS%')
        If OIColumns NE '' then
            Transfer OIColumns to OIColumnNames
        end else
            // The GetMappedOIColumnNames service will automatically remove any columns that are also mapped in
            // a join clause with the "Column Data" return value attribute.
            OIColumnNames   = SQL_Services_RevDotNet('GetMappedOIColumnNames', OITableName, SQLTableName, True$)
        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_RevDotNet('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>
                            IsOIKey         = False$    ; // Assume false for now.
                            If OIColumnType EQ 'F' then
                                OIColumnPos = FieldArray<FIELDS_FIELD_NO$, vPos>
                                If OIColumnPos GT 0 then
                                    OIColumnValue   = OIRow<OIColumnPos>
                                end else
                                    IsOIKey         = True$
                                    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 '') AND (IsOIKey NE True$) then
                                OIColumnValue   = OIColumnValue<0, AMVPosition>
                            end
                            Begin Case
                                Case SQLColumnType _EQC 'uniqueidentifier'
                                    OIColumnValue = 'NEWID()'
                                Case OIColumnValue EQ ''
                                    // Empty strings are treated as NULL values.
                                    OIColumnValue   = 'NULL'
                                Case InList('varchar,nvarchar,char,nchar', SQLColumnType, ',')
                                    // Single quote the value.
                                    SQLColumnLength = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnLength')
                                    OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                    If OIColumnConv NE '' then
                                        OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                    end
                                    If (SQLColumnLength EQ '') OR (SQLColumnLength EQ '-1') then SQLColumnLength = Len(OIColumnValue)
                                    OIColumnValue   = OIColumnValue[1, SQLColumnLength, 1]
                                    Swap "'" with "''" in OIColumnValue
                                    OIColumnValue   = "'" : OIColumnValue : "'"
                                Case InList('tinyint,smallint,bigint,int', SQLColumnType, ',')
                                    // Treat as an integer.
                                    OIColumnConv    = 'MD0'
                                    If Len(OIColumnConv) then
                                        OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                        If Not(Num(OIColumnValue)) then
                                            // Not a true number so format like a string.
                                            OIColumnValue   = "'" : OIColumnValue : "'"
                                        end
                                    end else
                                        // Empty value so treat it as a NULL value.
                                        OIColumnValue   = 'NULL'
                                    end
                                Case InList('decimal,numeric,float,real', SQLColumnType, ',')
                                    // Treat as a floating point number using any OI column output conversion if it
                                    // exists.
                                    OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                    Convert 'Z' to '' in OIColumnConv   ; // No empty strings with numeric columns.
                                    If Len(OIColumnConv) then
                                        OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                        If Not(Num(OIColumnValue)) then
                                            // Not a true number so format like a string.
                                            OIColumnValue   = "'" : OIColumnValue : "'"
                                        end
                                    end else
                                        // Empty value so treat it as a NULL value.
                                        OIColumnValue   = 'NULL'
                                    end
                                Case SQLColumnType _EQC 'bit'
                                    // Treat as a Boolean, but only return a 1, 0, or NULL.
                                    If OIColumnValue EQ '' then
                                        OIColumnValue = 'NULL'
                                    end else
                                        If OIColumnValue NE False$ then OIColumnValue = True$
                                    end
                                Case SQLColumnType _EQC 'date'
                                    OIColumnValue = "'" : (SRP_Date('Format', OIColumnValue, 'YYYY-MM-DD')) : "'"
                                Case SQLColumnType _EQC 'datetime'
                                    OIColumnValue = "'" : (SRP_DateTime('Format', OIColumnValue, 'YYYY-MM-DD hh:mm:ss')) : "'"
                                Case SQLColumnType _EQC 'time'
                                    OIColumnValue = "'" : (SRP_Time('Format', OIColumnValue, 'hh:mm:ss')) : "'"
                                Case Otherwise$
                                    // Treat as a character type.
                                    SQLColumnLength = SRP_JSON(MapObj, 'GETVALUE', 'SQLColumnLength')
                                    OIColumnConv    = FieldArray<FIELDS_CONV$, vPos>
                                    If OIColumnConv NE '' then
                                        OIColumnValue   = Fmt(OIColumnValue, OIColumnConv)
                                    end
                                    If (SQLColumnLength EQ '') OR (SQLColumnLength EQ '-1') then SQLColumnLength = Len(OIColumnValue)
                                    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
                            ColumnValues   := OIColumnValue : @FM

                            SRP_JSON(MapObj, 'RELEASE')
                        end
                        Error_Services('Clear') ; // Any errors at this point would have been created during calculated column logic, which is unrelated to this service.
                    end
                end
            While Error_Services('NoError')
            Next OIColumnName
        end
        ColumnValues[-1, 1] = ''
    end else
        Error_Services('Add', 'The OITableName, SQLTableName, OIKeyID, or OIRow argument was missing in the ' : Service : ' service.')
    end

    Response    = ColumnValues

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSQLInsertStatements
//
// OITableName  - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// OIKeyID      - The Key ID to the OpenInsight row being deleted from SQL. - [Required]
// OIRow        - The OpenInsight row associated with the OpenInsight Key ID. - [Optional]
// OIColumns    - An @FM delimited list of OpenInsight table columns that should only be used to write to SQL. If empty,
//                all columns that have a map defintion will be used. - [Optional]
//
// 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_RevDotNet('GetPrimarySQLTable', OITableName)
        MappedSQLColumnNames    = SQL_Services_RevDotNet('GetMappedSQLColumnNames', OITableName, PrimarySQLTableName, OIColumns)
        If MappedSQLColumnNames NE '' then MappedSQLColumnNames := @FM
        MappedSQLColumnValues   = SQL_Services_RevDotNet('GetMappedSQLColumnValues', OITablename, PrimarySQLTableName, OIKeyID, OIRow, '', OIColumns)
        If MappedSQLColumnValues NE '' then MappedSQLColumnValues := @FM
        JoinedSQLColumnNames    = SQL_Services_RevDotNet('GetMappedJoinedSQLColumnNames', OITableName, PrimarySQLTableName)
        If JoinedSQLColumnNames NE '' then
            MappedSQLColumnNames  := JoinedSQLColumnNames : @FM
            JoinedSQLColumnValues  = SQL_Services_RevDotNet('GetMappedJoinedSQLColumnValues', OITableName, PrimarySQLTableName, OIKeyID, OIRow)
            MappedSQLColumnValues := JoinedSQLColumnValues : @FM
        end
        StaticSQLColumnNames    = SQL_Services_RevDotNet('GetStaticSQLColumnNames', OITableName)
        If StaticSQLColumnNames NE '' then
            MappedSQLColumnNames  := StaticSQLColumnNames : @FM
            StaticSQLColumnValues  = SQL_Services_RevDotNet('GetStaticSQLColumnValues', OITableName)
            MappedSQLColumnValues := StaticSQLColumnValues : @FM
        end
        MappedSQLColumnNames[-1, 1]   = ''
        MappedSQLColumnValues[-1, 1]  = ''
        Swap @FM with ', ' in MappedSQLColumnNames
        Swap @FM with ", " in MappedSQLColumnValues
        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_RevDotNet('GetSecondarySQLTables', OITableName)
            If SecondarySQLTableNames NE '' then
                For Each SecondarySQLTableName in SecondarySQLTableNames using @FM
                    OIColumnValue = ''  ; // Default to empty string. Update if AMV Master is found.
                    NumPositions  = 1   ; // Default to only 1 index position. Update if AMV Master is found.
                    AMVMaster     = SQL_Services_RevDotNet('GetAMVMaster', OITableName, SecondarySQLTableName)
                    If AMVMaster NE '' then
                        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
                        end
                    end

                    MappedSQLColumnNames    = SQL_Services_RevDotNet('GetMappedSQLColumnNames', OITableName, SecondarySQLTableName)
                    If MappedSQLColumnNames NE '' then
                        JoinedSQLColumnNames    = SQL_Services_RevDotNet('GetMappedJoinedSQLColumnNames', OITableName, SecondarySQLTableName)
                        If JoinedSQLColumnNames NE '' then
                            MappedSQLColumnNames   := @FM : JoinedSQLColumnNames
                        end
                        Swap @FM with ', ' in MappedSQLColumnNames
                        For AMVPosition = 1 to NumPositions
                            If OIColumnValue<0, AMVPosition> NE '' then
                                MappedSQLColumnValues   = SQL_Services_RevDotNet('GetMappedSQLColumnValues', OITablename, SecondarySQLTableName, OIKeyID, OIRow, AMVPosition)
                                If JoinedSQLColumnNames NE '' then
                                    JoinedSQLColumnValues   = SQL_Services_RevDotNet('GetMappedJoinedSQLColumnValues', OITableName, SecondarySQLTableName, OIKeyID, OIRow, AMVPosition)
                                    MappedSQLColumnValues  := @FM : JoinedSQLColumnValues
                                end
                                Swap @FM with ', ' in MappedSQLColumnValues
                                SQLInsertStatement      = 'INSERT INTO ' : SecondarySQLTableName : ' '
                                SQLInsertStatement     := '('
                                SQLInsertStatement     := MappedSQLColumnNames
                                SQLInsertStatement     := ') VALUES ('
                                SQLInsertStatement     := MappedSQLColumnValues
                                SQLInsertStatement     := ')'
                                SQLInsertStatements    := SQLInsertStatement : @FM
                            end
                        Next AMVPosition
                    end
                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


//----------------------------------------------------------------------------------------------------------------------
// GetSQLUpdateStatements
//
// OITableName  - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// OIKeyID      - The Key ID to the OpenInsight row being deleted from SQL. - [Required]
// OIRow        - The OpenInsight row associated with the OpenInsight Key ID. - [Optional]
// OIColumns    - An @FM delimited list of OpenInsight table columns that should only be used to write to SQL. If empty,
//                all columns that have a map defintion will be used. - [Optional]
//
// Returns an @FM delimited list of SQL update statements for the SQL table mapped to the indicated OpenInsight table.
// This will automatically create all SQL update statements for secondary SQL tables as well.
//----------------------------------------------------------------------------------------------------------------------
Service GetSQLUpdateStatements(OITableName, OIKeyID, OIRow, OIColumns)

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

    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_RevDotNet('GetPrimarySQLTable', OITableName)
        JoinClause              = SQL_Services_RevDotNet('GetJoinClause', OITableName, PrimarySQLTableName, OIKeyID, OIRow)
        MappedSQLColumnNames    = SQL_Services_RevDotNet('GetMappedSQLColumnNames', OITableName, PrimarySQLTableName, OIColumns)
        If MappedSQLColumnNames NE '' then MappedSQLColumnNames := @FM
        MappedSQLColumnValues   = SQL_Services_RevDotNet('GetMappedSQLColumnValues', OITablename, PrimarySQLTableName, OIKeyID, OIRow, '', OIColumns)
        If MappedSQLColumnValues NE '' then MappedSQLColumnValues := @FM
        JoinedSQLColumnNames    = SQL_Services_RevDotNet('GetMappedJoinedSQLColumnNames', OITableName, PrimarySQLTableName)
        StaticSQLColumnNames    = SQL_Services_RevDotNet('GetStaticSQLColumnNames', OITableName)
        If StaticSQLColumnNames NE '' then
            MappedSQLColumnNames  := StaticSQLColumnNames : @FM
            StaticSQLColumnValues  = SQL_Services_RevDotNet('GetStaticSQLColumnValues', OITableName)
            MappedSQLColumnValues := StaticSQLColumnValues : @FM
        end
        MappedSQLColumnNames[-1, 1]   = ''
        MappedSQLColumnValues[-1, 1]  = ''
        SQLUpdateStatement  = ''
        If MappedSQLColumnNames NE '' then
            SQLUpdateStatement := 'UPDATE ' : PrimarySQLTableName : ' SET '
            For Each SQLColumnName in MappedSQLColumnNames using @FM setting fPos
                SQLUpdateStatement     := SQLColumnName : '=' : MappedSQLColumnValues<fPos> : ', '
            Next SQLColumnName
            SQLUpdateStatement[-2, 1]   = ''
            SQLUpdateStatement         := 'WHERE '
            SQLUpdateStatement         := JoinClause : ' '
            SQLUpdateStatements        := SQLUpdateStatement : @FM
        end

        If Error_Services('NoError') then
            // Add any secondary SQL table inserts that might be required.
            SecondarySQLTableNames  = SQL_Services_RevDotNet('GetSecondarySQLTables', OITableName)
            If SecondarySQLTableNames NE '' then
                For Each SecondarySQLTableName in SecondarySQLTableNames using @FM
                    OIColumnValue = ''  ; // Default to empty string. Update if AMV Master is found.
                    NumPositions  = 1   ; // Default to only 1 index position. Update if AMV Master is found.
                    AMVMaster     = SQL_Services_RevDotNet('GetAMVMaster', OITableName, SecondarySQLTableName)
                    If AMVMaster NE '' then
                        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
                        end
                    end

                    // Remove all rows from the secondary table. These will be added back using the INSERT statements as needed.
                    SQLUpdateStatement   = 'DELETE FROM ' : SecondarySQLTableName : ' '
                    SQLUpdateStatement  := 'WHERE '
                    SQLUpdateStatement  := JoinClause
                    SQLUpdateStatements := SQLUpdateStatement : @FM

                    MappedSQLColumnNames = SQL_Services_RevDotNet('GetMappedSQLColumnNames', OITableName, SecondarySQLTableName)
                    If MappedSQLColumnNames NE '' then
                        JoinedSQLColumnNames    = SQL_Services_RevDotNet('GetMappedJoinedSQLColumnNames', OITableName, SecondarySQLTableName)
                        If JoinedSQLColumnNames NE '' then
                            MappedSQLColumnNames   := @FM : JoinedSQLColumnNames
                        end
                        Swap @FM with ', ' in MappedSQLColumnNames
                        For AMVPosition = 1 to NumPositions
                            If OIColumnValue<0, AMVPosition> NE '' then
                                MappedSQLColumnValues   = SQL_Services_RevDotNet('GetMappedSQLColumnValues', OITablename, SecondarySQLTableName, OIKeyID, OIRow, AMVPosition)
                                If JoinedSQLColumnNames NE '' then
                                    JoinedSQLColumnValues   = SQL_Services_RevDotNet('GetMappedJoinedSQLColumnValues', OITableName, SecondarySQLTableName, OIKeyID, OIRow, AMVPosition)
                                    MappedSQLColumnValues  := @FM : JoinedSQLColumnValues
                                end
                                Swap @FM with ', ' in MappedSQLColumnValues
                                SQLUpdateStatement      = 'INSERT INTO ' : SecondarySQLTableName : ' '
                                SQLUpdateStatement     := '('
                                SQLUpdateStatement     := MappedSQLColumnNames
                                SQLUpdateStatement     := ') VALUES ('
                                SQLUpdateStatement     := MappedSQLColumnValues
                                SQLUpdateStatement     := ')'
                                SQLUpdateStatements    := SQLUpdateStatement : @FM
                            end
                        Next AMVPosition
                    end
                Next SecondarySQLTableName
            end
        end

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

    Response    = SQLUpdateStatements

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSQLSelectStatement
//
// OITableName  - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// OIKeyID      - The Key ID to the OpenInsight row being deleted from SQL. - [Required]
// OIRow        - The OpenInsight row associated with the OpenInsight Key ID. - [Optional]
//
// Returns the SQL select statement for the indicated SQL table based on its mapping to the indicated OpenInsight table.
//----------------------------------------------------------------------------------------------------------------------
Service GetSQLSelectStatement(OITableName, SQLTableName, OIKeyID, OIRow)

    Convert @Lower_Case to @Upper_Case in OITableName

    SQLSelectStatement  = ''

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

        JoinClause  = SQL_Services_RevDotNet('GetJoinClause', OITableName, SQLTableName, OIKeyID, OIRow, '', True$)
        If JoinClause NE '' then
            MappedSQLColumnNames    = SQL_Services_RevDotNet('GetMappedSQLColumnNames', OITableName, SQLTableName)
            If MappedSQLColumnNames NE '' then
                // Include the join columns among the columns being returned so their values can be updated in their
                // joined OpenInsight columns (if applicable).
                MappedSQLColumnNames   := @FM : SQL_Services_RevDotNet('GetMappedJoinedSQLColumnNames', OITableName, SQLTableName)
                Swap @FM with ', ' in MappedSQLColumnNames
                SQLSelectStatement     := 'SELECT '
                SQLSelectStatement     := MappedSQLColumnNames : ' '
                SQLSelectStatement     := 'FROM ' : SQLTableName : ' '
                SQLSelectStatement     := 'WHERE '
                SQLSelectStatement     := JoinClause : ' '
            end
        end

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

    Response    = SQLSelectStatement

end service


//----------------------------------------------------------------------------------------------------------------------
// GetSQLDeleteStatements
//
// OITableName  - The name of the OpenInsight table that is mapped to the SQL table. - [Required]
// OIKeyID      - The Key ID to the OpenInsight row being deleted from SQL. - [Required]
// OIRow        - The OpenInsight row associated with the OpenInsight Key ID. - [Optional]
//
// 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_RevDotNet('GetPrimarySQLTable', OITableName)
        JoinClause              = SQL_Services_RevDotNet('GetJoinClause', OITableName, PrimarySQLTableName, OIKeyID, OIRow, '', True$)
        SQLDeleteStatement      = 'DELETE FROM ' : PrimarySQLTableName : ' '
        SQLDeleteStatement     := 'WHERE '
        SQLDeleteStatement     := JoinClause
        SQLDeleteStatements    := SQLDeleteStatement : @FM

        // Add any secondary SQL table inserts that might be required.
        SecondarySQLTableNames  = SQL_Services_RevDotNet('GetSecondarySQLTables', OITableName)
        If SecondarySQLTableNames NE '' then
            For Each SecondarySQLTableName in SecondarySQLTableNames using @FM
                JoinClause              = SQL_Services_RevDotNet('GetJoinClause', OITableName, SecondarySQLTableName, OIKeyID, OIRow, '', True$)
                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


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