The following example demonstrates the complete workflow for retrieving a multi-table ProDataSet as an OUTPUT ProDataGraph, modifying rows with change tracking enabled, and sending the changes back to the application server as an INPUT ProDataGraph.

The ABL procedure GetCustOrders.p populates and returns an OUTPUT DATASET containing two related temp-tables (ttCustomer and ttOrder) linked by a data-relation on CustNum:

/*------------------------------------------------------------------------
    File        : GetCustOrders.p
    Purpose     : Returns customer and order rows as an 
                  OUTPUT DATASET (ProDataGraph).
                  Used by SendUpdates.java to demonstrate the
                  DATASET as ProDataGraph workflow.
  ----------------------------------------------------------------------*/

DEFINE TEMP-TABLE ttCustomer NO-UNDO
  FIELD CustNum   AS INTEGER
  FIELD Name      AS CHARACTER
  FIELD Comments  AS CHARACTER
  FIELD SalesRep  AS CHARACTER.

DEFINE TEMP-TABLE ttOrder NO-UNDO
  FIELD CustNum   AS INTEGER
  FIELD OrderNum  AS INTEGER
  FIELD SalesRep  AS CHARACTER.

DEFINE DATASET dsCustOrders FOR ttCustomer, ttOrder
  DATA-RELATION drCustOrder FOR ttCustomer, ttOrder
    RELATION-FIELDS (CustNum, CustNum).

DEFINE OUTPUT PARAMETER DATASET FOR dsCustOrders.

FOR EACH Customer NO-LOCK:
  CREATE ttCustomer.
  ASSIGN
    ttCustomer.CustNum   = Customer.CustNum
    ttCustomer.Name      = Customer.Name
    ttCustomer.Comments  = Customer.Comments
    ttCustomer.SalesRep  = Customer.SalesRep.

  FOR EACH Order NO-LOCK WHERE Order.CustNum = Customer.CustNum:
    CREATE ttOrder.
    ASSIGN
      ttOrder.CustNum   = Order.CustNum
      ttOrder.OrderNum  = Order.OrderNum
      ttOrder.SalesRep  = Order.SalesRep.
  END.
END.

The ABL procedure UpdateCustOrders.p accepts the modified DATASET as an INPUT parameter.

/*------------------------------------------------------------------------
    File        : UpdateCustOrders.p
    Purpose     : Accepts a DATASET of customers and orders where updates
                  have been made by the java client.
                  Used by SendUpdates.p.

  ----------------------------------------------------------------------*/

DEFINE TEMP-TABLE ttCustomer NO-UNDO
  FIELD CustNum   AS INTEGER
  FIELD Name      AS CHARACTER
  FIELD Comments  AS CHARACTER
  FIELD SalesRep  AS CHARACTER.

DEFINE TEMP-TABLE ttOrder NO-UNDO
  FIELD CustNum   AS INTEGER
  FIELD OrderNum  AS INTEGER
  FIELD SalesRep  AS CHARACTER.

DEFINE DATASET dsCustOrders FOR ttCustomer, ttOrder
  DATA-RELATION drCustOrder FOR ttCustomer, ttOrder
    RELATION-FIELDS (CustNum, CustNum).

DEFINE INPUT PARAMETER DATASET FOR dsCustOrders.

// This procedure receives only the changed rows which can then be applied
// to the database.

The Java client SendUpdates.java performs a three-step GET → MODIFY → UPDATE cycle:

  1. Define the schema — Creates a ProDataObjectMetaData for each temp-table in the dataset, then combines them into a ProDataGraphMetaData with a ProDataRelationMetaData that establishes the parent–child relationship between ttCustomer and ttOrder.
  2. GET — Calls GetCustOrders.p with ParamArray.addDataset() in OUTPUT mode. The application server returns the fully populated ProDataGraph.
  3. MODIFY — Enables change tracking by calling ProChangeSummary.beginLogging() on the graph, then modifies field values on both parent and child rows. The getChildRows() method navigates the data-relation to access related order rows. After all modifications, calls getChanges() to retrieve a changes-only ProDataGraph containing the updated data.
  4. UPDATE — Passes the changes-only ProDataGraph back to UpdateCustOrders.p as an INPUT DATASET parameter.
package com.progress.open4gl;

import com.progress.open4gl.javaproxy.OpenAppObject;
import com.progress.open4gl.javaproxy.Connection;
import com.progress.open4gl.javaproxy.ParamArray;
import com.progress.open4gl.javaproxy.ParamArrayMode;

import java.util.List;

public class SendUpdates {

  private static final String URL         = "<hosturl>";
  private static final String USER        = "";
  private static final String PWD         = "";
  private static final String APPINFO     = "";
  private static final int    TARGET_CUST = 4;

  public void example() throws Exception {

    // ── Schema ──────────────────────────────────────────────────────────
    ProDataObjectMetaData custMD = new ProDataObjectMetaData(
            "ttCustomer", 5, false, 0, null, null, null);
    custMD.setFieldMetaData(1,"CustNum", 0,Parameter.PRO_INTEGER,  0, 0);
    custMD.setFieldMetaData(2,"Name",    0,Parameter.PRO_CHARACTER,1, 0);
    custMD.setFieldMetaData(3,"City",    0,Parameter.PRO_CHARACTER,2, 0);
    custMD.setFieldMetaData(4,"Comments",0,Parameter.PRO_CHARACTER,3, 0);
    custMD.setFieldMetaData(5,"SalesRep",0,Parameter.PRO_CHARACTER,4, 0);

    ProDataObjectMetaData orderMD = new ProDataObjectMetaData(
            "ttOrder", 3, false, 0, null, null, null);
    orderMD.setFieldMetaData(1,"CustNum", 0,Parameter.PRO_INTEGER,  0,0);
    orderMD.setFieldMetaData(2,"OrderNum",0,Parameter.PRO_INTEGER,  1,0);
    orderMD.setFieldMetaData(3,"SalesRep",0,Parameter.PRO_CHARACTER,2,0);

    // Dataset: dsCustOrders with a parent-child relation on CustNum
    ProDataGraphMetaData graphMD = new ProDataGraphMetaData("dsCustOrders");
    graphMD.addTable(custMD);
    graphMD.addTable(orderMD);
    graphMD.addDataRelation(new ProDataRelationMetaData(
            "drCustOrder", 0, 1, 1, "CustNum,CustNum"));

    // ── Connection ──────────────────────────────────────────────────────
    Connection connection = new Connection(URL, USER, PWD, APPINFO);
    OpenAppObject appObj  = new OpenAppObject(connection, "");

    try {
      // ── Step 1: GET ──────────────────────────────────────────────────
      ParamArray getParams = new ParamArray(1);
      getParams.addDataset(0,(ProDataGraph) null, ParamArrayMode.OUTPUT,
        graphMD);

      appObj.runProc("GetCustOrders.p", getParams);

      ProDataGraph pdg = (ProDataGraph) getParams.getOutputParameter(0);

      List<ProDataObject> customers = pdg.getProDataObjects("ttCustomer");

      // ── Step 2: MODIFY ────────────────────────────────────
      // Turn change logging on with beginLogging()
      ProChangeSummary pchangeSumm;
      pchangeSumm = pdg.getProChangeSummary();
      pchangeSumm.beginLogging();

      for (ProDataObject cust : customers) {
        if (TARGET_CUST == (Integer) cust.get("CustNum")) {
          cust.set("Comments", 
            "All orders will be taken over by DKP Sales Rep");
          cust.set("SalesRep", "DKP");

          List<ProDataObject> orders = cust.getChildRows("drCustOrder");
          for (ProDataObject order : orders) {
            order.set("SalesRep", "DKP");
          }
          break;
        }
      }

      ProDataGraph changedProDGraph = pchangeSumm.getChanges();

      // ── Step 3: UPDATE ───────────────────────────────────────────────
      ParamArray updParams = new ParamArray(1);
      updParams.addDataset(0,changedProDGraph,ParamArrayMode.INPUT,graphMD);

      appObj.runProc("UpdateCustOrders.p", updParams);

    } finally {
        appObj._release();
    }
  }
}

The key differences from a single-table ProDataGraph are:

  • The ProDataGraphMetaData contains multiple ProDataObjectMetaData objects (one per temp-table).
  • A ProDataRelationMetaData links the parent and child tables, enabling getChildRows() navigation.
  • Change tracking via ProChangeSummary captures modifications so that only changed rows are sent back to the server via getChanges().
  • Uses ParamArray.addDataset() instead of addTable() when passing a multi-table ProDataGraph.