Release 10.1A: OpenEdge Development:
ProDataSets

> ## Passing a ProDataSet parameter by reference

> You can only specify `BY-REFERENCE` on an internal procedure or user-defined function call, not on a `RUN` of an external procedure. When you pass a ProDataSet parameter by reference, the called routine’s ProDataSet definition is bound to the calling routine’s ProDataSet only for the duration of the call.

> To pass a ProDataSet by reference, in the calling routine, use the `BY-REFERENCE` keyword in the `RUN` statement that defines the ProDataSet parameter. There is no special syntax required in the called routine. However, since the calling routine’s ProDataSet instance is substituted for the called routine’s ProDataSet, only the definition of the ProDataSet is required by the called routine. In other words, the same ProDataSet is defined in both routines but only one is actually used. If the called routine’s ProDataSet is not directly used to hold its own data anywhere within the routine, you can save the overhead of allocating it by including the `REFERENCE-ONLY` keyword on its definition. This keyword tells Progress to use the definition for compiler references to the table and its fields but not to instantiate it at run time. Any reference to the ProDataSet, except where it is passed in from another routine `BY-REFERENCE`, results in a runtime error.

> Because of the efficiency of passing the ProDataSet with `BY-REFERENCE`, you should normally use this keyword in your parameter definitions in `RUN` statements for any case where the called procedure will always or sometimes be in the same session as the caller. Because Progress ignores the keyword on a remote call, you get the most efficient behavior in either case.

> Why then is `BY-REFERENCE` not the default behavior?

> Passing objects by copying them is the standard for other Progress parameter types. Passing a ProDataSet by reference has certain side effects that you need to be aware of, which could make the behavior confusing if you are not conscious of what Progress is actually doing in the background to enable your called procedure to point to the same ProDataSet instance as the caller. For this reason, you have to make a specific request to pass by reference, and you should always make sure you have considered the consequences before doing so. Since this is different from how Progress behaves otherwise, some of the effects can seem counter-intuitive, so we explain them in some detail here.

> In general you must consider that on a call `BY-REFERENCE`, Progress substitutes the ProDataSet handle in the calling procedure for the ProDataSet defined in the called procedure, and that is the cause of most of the side effects. Consider the cases described in the following sections.

> ##### INPUT BY-REFERENCE can be like INPUT-OUTPUT

> In a local call, passing a ProDataSet as an `INPUT` parameter `BY-REFERENCE` makes it behave essentially like an `INPUT-OUTPUT` parameter. The called procedure uses its local definition of the ProDataSet, if any, only to verify that the definition is compatible with the `INPUT` parameter. It then adjusts any references to the parameter to point to the ProDataSet instance in the caller. As a result, any changes you make to data in the ProDataSet in the called procedure are actually made to the ProDataSet in the calling procedure, and so are visible there after the procedure returns.

> Design tip: If the called procedure can make changes that should be visible in the caller, then you should make the parameter `INPUT-OUTPUT`. This reflects what’s actually going on, and also provides the correct behavior when the call is remote, as the changes must explicitly be passed back to the caller in that case. If the called procedure will only read the data and not change it, then make the parameter `INPUT`.

> Why not always make the parameter `INPUT-OUTPUT` in this case?

> If you configure your application so that the call is made to a remote session and the called procedure doesn’t make any changes to the ProDataSet, then Progress will needlessly pass the unchanged ProDataSet back to the caller, creating unnecessary network traffic. This is why the `INPUT` mode is still useful.

> ##### OUTPUT BY-REFERENCE can be like OUTPUT APPEND

> If the ProDataSet parameter is `OUTPUT` `BY-REFERENCE` and the call is local, then any data added to the ProDataSet in the called procedure is effectively appended to what was already there, again because both procedures are actually pointing at the same ProDataSet instance. If you make the same call remotely or you don’t make it `BY-REFERENCE`, then the target ProDataSet in the calling procedure is emptied automatically by Progress before the data from the `OUTPUT` parameter is copied into it. This can give you different behavior between local and remote `RUN` statements.

> Design tip: When you pass an `OUTPUT` parameter `BY-REFERENCE`, and don’t want `APPEND` behavior, always explicitly empty the ProDataSet in the calling procedure just before the call.

> This might seem like extra overhead, but in fact if you execute the statement `hDataSet:EMPTY-DATASET()`, this executes exactly the same code internally as Progress uses to empty the ProDataSet for you, so the net cost is the same. If you do want `APPEND` behavior, then include the `APPEND` keyword on the parameter as you would for a temp-table.

> ##### ProDataSet instance passed BY-REFERENCE must exist in the caller

> Consider this situation where a ProDataSet is passed as an `OUTPUT` parameter. The calling procedure defines only a handle for the ProDataSet and passes it using the `DATASET-HANDLE` parameter form, without creating a dynamic ProDataSet first:

> Syntax
>     RUN called-procedure ( OUTPUT DATASET-HANDLE dataset-handle BY-REFERENCE ).

> The intention is that the `called-procedure’s` ProDataSet definition and data are passed back as the `OUTPUT` parameter to populate the `dataset-handle` in the caller. This cannot work properly when the call is local. When the call is made, the `dataset-handle` has the Unknown value (`?`) because it has not been used yet. But the `BY-REFERENCE` keyword instructs Progress to use the calling procedure’s ProDataSet as the basis for the parameter. Since there is no such ProDataSet at the time of the call, this results in an error. A ProDataSet passed `BY-REFERENCE`, regardless of the parameter mode, must be initialized by specifying its tables and relations before the call.

> In this case the calling procedure must do one of the following:

> - It can create a ProDataSet using the handle and use dynamic methods to create a structure of temp-tables and relations for it that are compatible with the ProDataSet in the `called-procedure`. In this way the calling procedure’s dynamic ProDataSet is used for the call, and the data from the `called-procedure` is effectively appended to it, just as for the static example described earlier.

> - It can omit the `BY-REFERENCE` keyword and pass the ProDataSet by value in all cases, which means that the `called-procedure’s` ProDataSet definition and data are copied back into the calling procedure to instantiate a dynamic ProDataSet there.

> - It can assign the handle to some valid existing static or dynamic ProDataSet before the call.

> If the `OUTPUT` parameter is a `DATASET-HANDLE`, it presumably means that the calling procedure is prepared to accept a variety of ProDataSet definitions returned to it. If this is the case, then once a particular ProDataSet has been passed back and has populated the dynamic ProDataSet, any further calls using the same `RUN` statement form must receive back a ProDataSet of the same type. Even though Progress empties the target dynamic ProDataSet so that the data from the `called-procedure` replaces any data in the calling procedure’s ProDataSet, Progress does not automatically delete the dynamic ProDataSet structure in the calling procedure. If it is not compatible with the `OUTPUT` parameter, an error results.

> If you want to use the same dynamic ProDataSet handle to receive different ProDataSets during the lifetime of the calling procedure, you must delete the dynamic ProDataSet using the `DELETE OBJECT` statement. Set the handle variable to the Unknown value (`?`) before you run a `called-procedure` to get back a different ProDataSet to avoid the error.

> Sometimes the calling procedure needs to get back a dynamic ProDataSet (that is, one with a variable definition) on the first call and then wants to be able to make further calls to get back new or additional data in the same ProDataSet type, but without copying the ProDataSet definition locally on subsequent calls. If this is the case, then the first `RUN` statement must be made by value, in order to get back the ProDataSet definition with the data, and then subsequent calls can be made with a separate `RUN` statement `BY-REFERENCE`. This avoids copying the ProDataSet definition locally. (Note that in these descriptions the words “by value” are normally not capitalized in order to emphasize that this is the default behavior, so that the keyword `BY-VALUE` is not required to get this behavior.)

> Naturally you could not pass an uninitialized dynamic ProDataSet as an `INPUT` or `INPUT`-`OUTPUT` parameter, as there would be no definition to pass to the `called-procedure`. In such a case you could only pass the handle variable and let the `called-procedure` associate it with a ProDataSet of its own.

> Design tip: To summarize: if the target ProDataSet in the calling procedure is dynamic, then you must either initialize it before making a call to another local procedure `BY-REFERENCE`, or make the first call by value and subsequent calls `BY-REFERENCE` so that you obtain the ProDataSet definition the caller needs.

> ##### Main block references ignored in internal procedures

> When the called procedure is a persistent procedure, its ProDataSet definition will naturally be in the main block of the procedure, that is, outside of any internal procedure. A ProDataSet definition is in fact not even allowed in an internal procedure. However, if its internal procedures are ever passed a ProDataSet parameter `BY-REFERENCE`, it is important that you not reference the ProDataSet handle in any way in the main block if you expect the effect of that reference to be visible in the internal procedures. This case is insidious enough to merit a specific example and diagram.

> Here’s a simple procedure that defines the `dsOrder` ProDataSet, runs another procedure persistent, and then runs an internal procedure to fill the ProDataSet:

>     /* refCaller.p */
>     {dsOrderTT.i}
>     {dsOrder.i}
>     DEFINE VARIABLE hProc AS HANDLE     NO-UNDO.
>     RUN refCallee.p PERSISTENT SET hProc.
>     RUN fillProc IN hProc (OUTPUT DATASET dsOrder ).

> Here’s the procedure it runs. It defines its own instance of the ProDataSet and then uses its handle to attach three Data-Sources. Inside the internal procedure `fillProc`, it fills the ProDataSet and returns it as an `OUTPUT` parameter, as shown:

>      /* refCallee.p */
>      {dsOrderTT.i}
>      {dsOrder.i}
>      DEFINE VARIABLE hDset AS HANDLE     NO-UNDO.
>      DEFINE DATA-SOURCE srcOrder FOR Customer.
>      DEFINE DATA-SOURCE srcOline FOR OrderLine.
>      DEFINE DATA-SOURCE srcItem FOR ITEM.

>         hDset = DATASET dsorder:HANDLE.
>         hDset:GET-BUFFER-HANDLE(1):ATTACH-DATA-SOURCE(DATA-SOURCE
>     srcOrder:HANDLE).
>         hDset:GET-BUFFER-HANDLE(2):ATTACH-DATA-SOURCE(DATA-SOURCE
>     srcOline:HANDLE).
>         hDset:GET-BUFFER-HANDLE(3):ATTACH-DATA-SOURCE(DATA-SOURCE
>     srcItem:HANDLE).
>      PROCEDURE fillProc:
>         DEFINE OUTPUT PARAMETER DATASET FOR dsOrder.
>         DATASET dsOrder:FILL().
>      END PROCEDURE.

> If you run the main procedure `refCaller.p`, you get the following error:

> What happened? All three Data-Sources were attached in the main block, so why can’t Progress see them?

> The reason is that the instance of `dsOrder` defined in the main block, the one whose handle was used to attach the Data-Sources, isn’t the one used by the internal procedure. Because the ProDataSet is passed in by reference, `fillProc` is pointing to the instance of `dsOrder` defined in `refCaller.p`, which has no Data-Sources. A few messages confirm this.

> Let’s display the ProDataSet handle in the calling procedure:

>      RUN refCallee.p PERSISTENT SET hProc.
>      MESSAGE "In the calling proc, dsOrder is " DATASET dsOrder:HANDLE
>             VIEW-AS ALERT-BOX.
>      RUN fillProc IN hProc (OUTPUT DATASET dsOrder BY-REFERENCE).

> Also, in the main block of the persistent procedure `refCallee.p`:

>     MESSAGE "In the main block, dsOrder is " DATASET dsOrder:HANDLE
>             VIEW-AS ALERT-BOX.

> And, in the internal procedure `fillProc`:

>     PROCEDURE fillProc:
>         DEFINE OUTPUT PARAMETER DATASET FOR dsOrder.
>         MESSAGE "In the fillProc, dsOrder is " DATASET dsOrder:HANDLE
>             VIEW-AS ALERT-BOX.
>         DATASET dsOrder:FILL().
>      END PROCEDURE.

> Run `refCaller.p` again and you can see the proof. When `refCallee.p` is first run, it gets a handle for its own ProDataSet:

> Next, the calling procedure displays the handle of its copy of the ProDataSet:

> Now it runs `fillProc`:

> You can see that `fillProc’s` ProDataSet has the same handle as the one in the calling procedure `refCaller.p`. In fact, it is the same ProDataSet, the one with no Data-Sources.

> If you change the persistent procedure to do all its work in the internal procedure, then everything works, as shown:

>     /* refCallee.p */
>      {dsOrderTT.i}
>      {dsOrder.i}
>      DEFINE DATA-SOURCE srcOrder FOR Customer.
>      DEFINE DATA-SOURCE srcOline FOR OrderLine.
>      DEFINE DATA-SOURCE srcItem FOR ITEM.

>      PROCEDURE fillProc:
>         DEFINE OUTPUT PARAMETER DATASET FOR dsOrder.
>         DEFINE VARIABLE hDset AS HANDLE     NO-UNDO.
>         hDset = DATASET dsorder:HANDLE.
>         hDset:GET-BUFFER-HANDLE(1):ATTACH-DATA-SOURCE(
>                                                   DATA-SOURCE srcOrder:HANDLE).
>         hDset:GET-BUFFER-HANDLE(2):ATTACH-DATA-SOURCE(
>                                                   DATA-SOURCE srcOline:HANDLE).
>         hDset:GET-BUFFER-HANDLE(3):ATTACH-DATA-SOURCE(
>                                                   DATA-SOURCE srcItem:HANDLE).
>         DATASET dsOrder:FILL().
>      END PROCEDURE.

> Figure 2–1 shows what’s happening.

> Figure 2–1: Passing ProDataSets

> Procedure `refCallee.p` has a definition of `dsOrder`, but the ProDataSet instance this represents is replaced by the one from `refCaller.p` when its ProDataSet is passed `BY-REFERENCE`. All internal references to `hDset` are therefore invalid because they point to a ProDataSet instance that isn’t being used. This teaches two important lessons, as described in the following design tips.

> Design tip: Don’t set ProDataSet handles at the main procedure level when they will be accessed in internal procedures. Set the handle variables where they are used to capture a reference to an externally defined ProDataSet.

> Design tip: It’s always good practice to perform operations such as attaching Data-Sources locally to where they are needed. It’s essential if the ProDataSet is being passed by reference.

> Note: These procedures use the standard include files for the temp-table and ProDataSet definitions. Adding the `REFERENCE-ONLY` keyword to these definitions would improve the performance of these procedures by avoiding the instantiation of the called routine’s objects. It would also avoid the run-time errors by telling Progress at compile time that the called procedure's ProDataSet is not actually being used.

> ##### Specifying BY-VALUE in the called procedure

> The parameter list shared by the calling procedure and the called procedure represents a contract between the two procedures that defines how they exchange data. As the cases we explored above illustrate, passing a ProDataSet `BY-REFERENCE` is a valuable optimization but one with side effects that change the nature of the contract between caller and callee. In some cases, the called procedure might want to force a ProDataSet parameter to be passed by value, regardless of any optimization used by the caller, to enforce the contract of its parameter list, and to avoid some of the side effects that can occur. For example, the called procedure might have some reason why it has to reference the ProDataSet handle in its main block and have that handle retain its validity inside internal procedures. Or, it might need to insist that an `INPUT` parameter should not result in the caller being able to see changes made to the ProDataSet in the called procedure. In any such case, the called procedure can include the `BY-VALUE` keyword in its parameter definition to force the ProDataSet to be passed by value, regardless of the caller, as shown:

>     DEFINE [ INPUT | OUTPUT | INPUT-OUTPUT ] PARAMETER DATASET FOR dataset-name
>        BY-VALUE.
>     Or
>     DEFINE [INPUT | OUTPUT | INPUT-OUTPUT] PARAMETER DATASET-HANDLE dataset-handle
>        BY-VALUE.

> ##### Importance of optimized code with BY-REFERENCE

> Note: The information in this section applies to the use of BIND as well, which you will read about in the "Passing a ProDataSet parameter by binding" section.

> This diversion into a discussion of `BY-REFERENCE` might seem overly complex, but it has been introduced here for a reason. If you get into the habit of structuring your procedures and their ProDataSet parameters properly from the beginning, you will be well positioned to optimize most calls that are sometimes made locally and sometimes remotely by adding the `BY-REFERENCE` keyword to your calls without any undesirable consequences. It is always best to design your procedures so that they work properly when run locally—even when in a deployed application they may be distributed on different machines. This makes the initial development and testing of your application more straightforward, and supports the case (even if it’s only for demo purposes) where everything is running in a single session. Since the `BY-REFERENCE` optimization is such a valuable one, it is worth making the effort to prepare for it right from the start, even if you first code and test your application without it and then add the keyword to your calls to improve performance.

> You can pass a ProDataSet reference locally as a `HANDLE PARAMETER` as you can with other objects such as temp-tables. This gives the called procedure access to the ProDataSet instance defined in the caller, but has two essential limitations. First, the called procedure must be in the same Progress session as the calling procedure. You can’t pass an object handle of any kind across an AppServer call and have it maintain its validity. Second, the called procedure can only reference the ProDataSet using dynamic attributes and methods if it receives it as a `HANDLE`. In other words, the called procedure can’t receive the `HANDLE` parameter into a static `DATASET` definition and reference it using static table and field names.

> By contrast, when you pass or receive a ProDataSet as a `DATASET-HANDLE`, this simply means that the procedure using the `DATASET-HANDLE` parameter form sees the ProDataSet as a dynamic object. The procedure on the other side of the call can see it as a static object. The table below shows the different combinations. Normally, passing a ProDataSet as a `DATASET-HANDLE` causes its definition and data to be copied to the receiving procedure. Using the `BY-REFERENCE` option on the parameter makes passing a ProDataSet as a `DATASET-HANDLE` cost no more within a single session than passing a reference to it as a `HANDLE`. For this reason, we recommend that you normally use the `DATASET-HANDLE` form. In this way, if the call is ever moved to a remote procedure, it will still work properly, whereas the `HANDLE` parameter will fail on a remote call.

> Passing a ProDataSet `BY-REFERENCE` is particularly valuable when the called procedure uses the `DATASET` parameter form to receive the ProDataSet into a static definition. This will be the norm in most server-side procedures, and especially in most event handling procedures. The business logic in these procedures is much simpler to write if it uses static 4GL statements to refer to and manipulate the records in the ProDataSet’s temp-tables. All the elements of the ProDataSet definition, along with all the data in its temp-tables, are available to the called procedure. The default buffers that are part of the ProDataSet, which have the same names as their temp-tables, are in fact shared between caller and callee. Therefore, if the called procedure changes the record position in any of these buffers, this will be visible to the caller after the procedure returns. The called procedure can define buffers of its own to avoid this. Any changes to the ProDataSet data made by the called procedure are visible to the caller, as if the object were `SHARED`.

> There are several restrictions in ProDataSet usage that are at least partially related to this parameter support:

> - You cannot define a ProDataSet inside an internal procedure or trigger. This is the same restriction that applies to temp-tables.

> - The static parameter form (that is, `DEFINE` `INPUT`/`OUTPUT` `PARAMETER DATASET` `FOR` `dataset-name`) cannot be used in the main block of a procedure that is run `PERSISTENT`.

> As a final note, it is possible to specify a ProDataSet as a parameter for a dynamic `CALL` object, which lets you set up the entire definition of a `RUN` statement dynamically. You can also specify the `BY-REFERENCE` qualifier in the arguments to the dynamic `CALL`. For more information, see the reference documentation or online help for the `SET-PARAMETER( )` method on the dynamic `CALL` object.

Copyright © 2005 Progress Software Corporation www.progress.com Voice: (781) 280-4000 Fax: (781) 280-4095