New API for dynamic DATA-SOURCE objects

The articles Filtering in a Data Access class on a table that is not part of the (default) data-source and Switching the Buffer sequence on a DATA-SOURCE in a DataAccess class demonstrate how to use dynamic queries, buffers and data-source object handles and to use custom queries for a Data Access classes data access.

With release 2016-06-17 we have release a new API to simplify this. The new API only requires a developer to name a list of database tables and builds all required dynamic objects internally. The API is based on the Consultingwerk.OERA.BufferDataSource and Consultingwerk.OERA.BufferSpec classes.

The code samples below demonstrate how to use the BufferDataSource class. The sample demonstrates the Data Access class of a simple Customer Business Entity that should be filtered on Customer, or an Item Number that was ordered by a customer or the Sales Rep's Name.

Variable definition

The implementation requires the definition of a class wide private variable. This variable is required to hold a reference to the BufferDataSource instance while FetchData in the DataAccess is executed.

DEFINE VARIABLE oBufferDataSource AS IBufferDataSource NO-UNDO .

FetchData Implementation

The implementation of the FetchData method uses the QueryParser class to inspect the query string for certain fields. In this case, the QueryParser is used to check if the query string contains the fields eCustomer.ItemNum or eCustomer.RepName. But fields to not exist in the Business Entity temp-table but can still be specified in the query string. When one of the fields if found, an Instance of the BufferDataSource class is created providing the appropriate database tables or BufferSpec instances as a parameter. Providing BufferSpec instances allows to use custom buffer names in the data-source query.

The FINALLY block in the method cleans up the BufferDataSource instance.

    METHOD OVERRIDE PUBLIC VOID FetchData (poFetchDataRequest AS Consultingwerk.OERA.IFetchDataRequest):
 
        DEFINE VARIABLE oQueryParser     AS QueryParser      NO-UNDO .
        DEFINE VARIABLE oQueryExpression AS IQueryExpression NO-UNDO .
        DEFINE VARIABLE cQueryString     AS CHARACTER        NO-UNDO .
 
        /* Only try to filter by eCustomer.ItemNum or eCustomer.RepName, when eCustomer is the first requested table */
        IF ENTRY (1, THIS-OBJECT:ExpandTables (poFetchDataRequest:Tables)) = "eCustomer":U THEN DO:
 
            cQueryString = ENTRY (1, poFetchDataRequest:Queries, CHR(1)) .
 
            /* Parse QueryString of the request into a list of query predicates */
            oQueryParser = NEW QueryParser () .
            oQueryExpression = oQueryParser:ParseQueryString (cQueryString) .
 
            /* Filter by ItemNum ? */                                          /* In the client query, it's ways filtered relatively to the TT's */
            IF VALID-OBJECT (QueryExpression:FindQueryExpressionForBufferField (BufferHelper:ParseFieldName ("eCustomer.ItemNum":U),
                                                                                oQueryExpression)) THEN
                ASSIGN oBufferDataSource = NEW BufferDataSource ("OrderLine",
                                                                 "Order",
                                                                 "Customer") .
            ELSE
 
            /* Filter by RepName ? */                                          /* In the client query, it's ways filtered relatively to the TT's */
            IF VALID-OBJECT (QueryExpression:FindQueryExpressionForBufferField (BufferHelper:ParseFieldName ("eCustomer.RepName":U),
                                                                                oQueryExpression)) THEN
                ASSIGN oBufferDataSource = NEW BufferDataSource (NEW BufferSpec ("sports2000.SalesRep", "Vertriebler"),
                                                                 NEW BufferSpec ("Customer")) .
        END.
 
        SUPER:FetchData (poFetchDataRequest).
 
        FINALLY:
            GarbageCollectorHelper:DeleteObject(oBufferDataSource) .
        END FINALLY.
    END METHOD.

AttachDataSource Implementation

In the AttachDataSource method we are conditionally replacing the data-source of the temp-table buffer. This is done based on the existence of a valid oBufferDataSource reference. AttachDataSource is executed from within the FetchData method. 

    METHOD OVERRIDE PROTECTED VOID AttachDataSources ():
 
        DEFINE VARIABLE cMapping AS CHARACTER NO-UNDO.
 
        Consultingwerk.Util.DatasetHelper:SetTrackingChanges (DATASET dsCustomer:HANDLE, FALSE) .
 
        @AttachDataSourcesStart.
        THIS-OBJECT:AttachDataSource (BUFFER eCustomer:HANDLE,
                                      DATA-SOURCE src_Customer:HANDLE, "
                                      CustNum,Customer.CustNum,
                                      Country,Customer.Country,
                                      Name,Customer.Name,
                                      Address,Customer.Address,
                                      Address2,Customer.Address2,
                                      City,Customer.City,
                                      State,Customer.State,
                                      PostalCode,Customer.PostalCode,
                                      Contact,Customer.Contact,
                                      Phone,Customer.Phone,
                                      SalesRep,Customer.SalesRep,
                                      CreditLimit,Customer.CreditLimit,
                                      Balance,Customer.Balance,
                                      Terms,Customer.Terms,
                                      Discount,Customer.Discount,
                                      Comments,Customer.Comments,
                                      Fax,Customer.Fax,
                                      EmailAddress,Customer.EmailAddress":U) .
        @AttachDataSourcesEnd.
 
        /* Replace Data-Source with dynamic one */
 
        IF VALID-OBJECT (oBufferDataSource) THEN DO:
            ASSIGN cMapping = BUFFER eCustomer:DATA-SOURCE-COMPLETE-MAP .
            BUFFER eCustomer:DETACH-DATA-SOURCE () .
            THIS-OBJECT:AttachDataSource(BUFFER eCustomer:HANDLE, oBufferDataSource, cMapping) .
        END.

    END METHOD.

SourceColumn method

At the begin of the SourceColumn method we are now mapping the fields from the alternative database tables (OrderLine, SalesRep):

        CASE pcColumn:
            WHEN "eCustomer.ItemNum" THEN
                RETURN "OrderLine.ItemNum".
            WHEN "eCustomer.RepName" THEN
                RETURN "SalesRep.Repname" .
        END CASE .

SourceDefaultQuery method

Within SourceDefaultQuery we are now also using the oBufferDataSource variable to test if we are using a custom (dynamic) data-source object handle. The BufferDataSource instance provides a method to return a standard SourceDefaultQuery.

    METHOD OVERRIDE PUBLIC CHARACTER SourceDefaultQuery (pcTable AS CHARACTER):
 
        IF pcTable = "eCustomer" AND VALID-OBJECT (oBufferDataSource) THEN
            RETURN oBufferDataSource:SourceDefaultQuery() .
 
        @SourceDefaultQueryCaseBlock.
        CASE pcTable:
            WHEN "eCustomer":U THEN
                RETURN "FOR EACH Customer INDEXED-REPOSITION":U.
        END CASE .
    END METHOD.