Project

General

Profile

Record Buffer Definition and Scoping

The concept of a record buffer is central to data access in the Progress 4GL. A record buffer acts as the bridge between the Progress database and application business logic. A record buffer is created implicitly by the Progress runtime environment whenever business logic accesses an existing database record or creates a new one. A record buffer also can be created explicitly using certain language statements. Each record buffer can hold a single record at a time, or it can be empty. A record buffer is a holding area for a record newly created by business logic before it is persisted in a database, or for a record retrieved from a database, so that it can be read, updated, or deleted by application logic.

Buffers are referenced via a name. That name may have been explicitly defined in the current external procedure, internal procedure or function using one of the following language constructs:

DEFINE BUFFER {buffer} FOR {record}

DEFINE PARAMETER BUFFER {buffer} FOR {record}

FUNCTION ... (BUFFER {buffer} FOR {record}, ...)

If not explicitly defined, then the buffer name must be implicitly defined based on a reference to a table name (persistent table, temp-table or work-table) inside a language statement or built-in function.

In the 4GL, record buffers operate within various record scopes. A record scope (a.k.a, buffer scope) corresponds with some block of enclosing code, which varies depending upon the ways in which the buffer is accessed by the program. The scope determines what happens to records which are stored in the buffer, and when certain events occur. For instance, a buffer's scope determines when a newly created record is inserted into the database or a modified record is saved (for create and update operations, respectively). It determines when a new record is read from the database into the buffer (for read operations), and how undo operations are conducted. Scopes can be nested, further complicating the behavior.

Progress manages buffer scopes implicitly, based upon many factors. Seemingly innocuous changes in a program can lead to unusual and often unintended changes in a record scope, resulting in unexpected variations in buffer behavior. This implicit handling makes record scoping one of the more challenging aspects of Progress programming. As we will see below, the rules determining record scopes are quite complicated, and are not well documented elsewhere.

FWD Record Buffer Implementation Classes

The concept of a record buffer is maintained in the FWD runtime environment. The class com.goldencode.p2j.persist.RecordBuffer implements buffers associated with persistent tables and com.goldencode.p2j.persist.TemporaryBuffer implements those associated with temporary tables. These classes enable the basic CRUD (Create, Read, Update, Delete) services, and implement numerous legacy, database-related, built-in functions (for a detailed explanation of how 4GL database functions map to their FWD equivalents, please refer to the Database Field References chapter of this book).

The use of record buffers and particularly their scopes is more explicit in Java source code converted by FWD than in the original Progress code. All buffers are defined explicitly via Data Model Objects (DMOs), and their scopes are opened explicitly. Scopes are closed again automatically at the end of the block of code for which they are opened. This ensures that the source code is self-documenting with respect to record buffers and their corresponding scopes.

Instances of RecordBuffer and its subclass TemporaryBuffer are neither created nor accessed directly by converted application code. Rather, DMO instances are defined/created in Java code, and the FWD runtime creates an associated instance of RecordBuffer (for a persistent table buffer) or of TemporaryBuffer (for a temporary table buffer). The buffer instance acts as a worker object behind the scenes to provide persistence services to application logic through the associated DMO. These services are accessed either by calling instance methods of the target DMO directly (to get or set individual record fields, for example), or by invoking static methods of RecordBuffer/TemporaryBuffer, passing the target DMO as a parameter to such methods (to create a new record or to test whether a record currently exists in the buffer, for instance). Although the actual worker object (RecordBuffer or TemporaryBuffer) is hidden behind the DMO, the explicit creation of the DMO makes it obvious when a buffer is created, and all scopes are opened explicitly via the RecordBuffer.openScope static method.

Note that although the RecordBuffer/TemporaryBuffer instance and the DMO it backs are technically separate objects, they are treated as interchangeable by various FWD APIs. Thus, many APIs which call for a “buffer” will accept a generic parameter. The FWD conversion will emit a DMO reference into converted code for these parameters.

There are various ways to define a record buffer in FWD. The mechanism used in any given case depends upon the 4GL code being converted. The Progress language constructs which cause buffers to be defined and the mechanisms used to define them in FWD are discussed in the following sections. The syntax diagrams for the Progress statements below are provided for convenience and reference only for the purpose of discussing the output of the FWD conversion. For additional details about the Progress language itself, please refer to the original Progress documentation.

DEFINE BUFFER

With some exceptions, most references to a table or to a field in a 4GL program will result in the implicit creation of a record buffer. To create a named buffer explicitly, the DEFINE BUFFER language statement is used. The FWD conversion supports various permutations of the DEFINE BUFFER statement.

The basic syntax for this statement is as follows:

DEFINE BUFFER
   [ [ NEW ] SHARED ] [ PRIVATE | PROTECTED ] BUFFER <buffer>
   FOR [ TEMP-TABLE ] <table>
   [ PRESELECT ]
   [ LABEL ] <label>
   [ NAMESPACE-URI ] <namespace> [ NAMESPACE-PREFIX ] <prefix>

where:

<buffer> is the name of the buffer being defined

<table> is the table for which the buffer is being defined

<label> is a label used for the buffer in error messages

<namespace> and <prefix> refer to an optional namespace associated with the buffer

While the FWD parser parses all of the possible options associated with the DEFINE BUFFER statement, the full conversion does not support every option. Specifically, the PRIVATE and PROTECTED options, which have to do with Progress' object oriented programming support, currently are ignored, as are the PRESELECT, LABEL, and namespace-related options.

Define a Buffer for a Persistent Table

In its simplest form, DEFINE BUFFER is used to define a buffer for a persistent table. For example, consider a testdb database with a customer table and the following statement.

Example 1:

DEFINE BUFFER x-customer FOR customer.

Converted code:

Customer xCustomer = RecordBuffer.define(Customer.class, "testdb", "xCustomer");

Details:

The above conversion assumes default name conversion rules are in use. The xCustomer variable appears as an instance member of the Java class representing the converted, external procedure.

The Java code declares and initializes a variable xCustomer of type Customer. Customer is the DMO interface which represents records from the customer table in the testdb database. A label of &quot;xCustomer" is used to keep track of the DMO within the runtime.

Note the use of the RecordBuffer static method:

public static <T> T define(Class<T> dmoIface,
                           String   database,
                           String   variable)

This method accepts the type (class) of a DMO to be created, the logical name or alias of the associated database, and the name of the variable which represents this DMO. This last parameter must be unique within the context of the containing class (i.e., the Java class which corresponds to the converted, external procedure). It uniquely identifies the DMO within the currently executing program for purposes of scope management, error reporting, and many internal housekeeping functions.

The RecordBuffer.define method returns a DMO instance which is linked to a new instance of the RecordBuffer class. The RecordBuffer instance is not exposed to application logic.

Define a Buffer for a Temp-Table

When the DEFINE BUFFER statement is used with temporary tables, the converted outcome differs.

Example 2:

Consider the following temp-table definition:

DEFINE TEMP-TABLE tmp-customer
   FIELD cust-num AS INT
   FIELD name AS CHAR.

Now consider the following DEFINE BUFFER statement which - as in Example 1 above - is in its simplest form (i.e., no options):

DEFINE BUFFER x-customer FOR tmp-customer.

...or its functional equivalent:

DEFINE BUFFER x-customer FOR TEMP-TABLE tmp-customer.

Note that the TEMP-TABLE keyword above is syntactic sugar, which is not considered by the conversion rules.

Converted code:

TempRecord1 xCustomer = TemporaryBuffer.define(TempRecord1.class, "xCustomer", false);

Details:

FWD converts both of these 4GL statements into the same Java code. Even though the DEFINE BUFFER statement here is nearly identical to Example 1, the fact that tmp-customer represents a temp-table rather than a persistent table triggers a different converted result.

An instance variable of xCustomer of type TempRecord1 is declared and initialized. TempRecord1 is the DMO interface representing records of the converted tmp-customer temp-table 1 . A label of "xCustomer" is used by the runtime to manage this DMO and its associated buffer.

In this case, we use the following, static method of TemporaryBuffer to define the DMO and to create a backing buffer:

public static <T extends Temporary> T define(Class<T> dmoIface,
                                             String   variable,
                                             boolean  global)

This method accepts the type (class) of the DMO to be created, the name of the variable which represents the DMO (again, this must be unique within the context of the enclosing program), and a boolean flag indicating whether the temporary table backing the DMO is global (i.e., survives beyond the end of the external procedure in which it is defined).

The global parameter is always false for a converted DEFINE BUFFER statement. This parameter exists because this TemporaryBuffer.define method also is used by the FWD conversion when defining the implicit, default buffer associated with a DEFINE TEMP-TABLE statement. The latter statement has an option to define a temporary table as global.

In this case, since the backing table is a temp-table, the buffer object for the created DMO is an instance of the TemporaryBuffer class rather than the RecordBuffer class.

1 This example assumes tmp-customer was the first temp-table encountered during conversion. The naming convention for temp-table DMO types uses the template TempRecordN, where the value of N is determined by a counter managed by the conversion logic. Each new, unique, temp-table definition encountered causes this counter to be incremented. See the Data Model Objects chapter for additional information.

Define a Shared Buffer

The NEW and SHARED keyword options to the DEFINE BUFFER statement are used to share buffers across external procedures. To define a shared temp-table that will be used by other procedures, the NEW keyword is used with the SHARED keyword.

Example 3:

DEFINE NEW SHARED BUFFER x-customer FOR customer.

Converted code:

Customer xCustomer = RecordBuffer.define(Customer.class, "testdb", "xCustomer");

...

public void execute()
{
   externalProcedure(new Block()
   {
      public void body()
      {
         ...
         SharedVariableManager.addBuffer("xCustomer", xCustomer);
         ...
      }
   });
}

Details:

The declaration and initialization of the xCustomer instance variable is identical to the previous example of a simple buffer for a persistent table.

In addition, within the main execute() method of the converted program, the xCustomer DMO instance is added as a shared buffer to the SharedVariableManager, using the static method addBuffer(see the discussion of Shared Variables in the Data Types chapter):

public static <T> void addBuffer(String name, T buffer)

where:

name is the label by which the shared buffer is uniquely identified by code which accesses the shared buffer while it is in scope;

buffer is the DMO which is associated with the shared buffer.

Storing the DMO in this manner will allow it to be looked up and accessed later by other, converted procedures which are launched (either directly or indirectly) by this program. In 4GL code, this would be achieved using the DEFINE SHARED BUFFER (i.e., no NEW keyword) in the called procedure, using the same buffer name and table.

Example 4:

DEFINE SHARED BUFFER x-customer FOR customer.

Converted code:

Customer xCustomer = SharedVariableManager.lookupBuffer("xCustomer");

where xCustomer is an instance variable of the Java class representing the converted external procedure.

Details:

When processing the above DEFINE SHARED BUFFER statement, the FWD conversion understands that the x-customer shared buffer already was created and shared by another program, somewhere up the call stack. As such, it determines that a TemporaryBuffer.define call is not needed in this case. Rather, it emits the above Java code to look up the associated xCustomer DMO instance by name from the SharedVariableManager. Since the same instance of the xCustomer DMO is used in both the calling and in the called program, all changes made to that DMO are automatically shared.

DEFINE TEMP-TABLE (WORK-TABLE, WORKFILE)

Defining a temp-table (or work-table/workfile) in Progress code implicitly defines a default buffer with the same name as the temp-table. The FWD conversion makes these buffer definitions explicit, using the TemporaryBuffer.define method.

For the purpose of this discussion, the portions of the DEFINE [TEMP-TABLE | WORK-TABLE | WORKFILE] statements which define the structure of temp-tables are ignored. This structural aspect of temp-table conversion is handled during parsing and schema conversion; please refer to the Schema Conversion chapter of this document for those details.

The beginning portion of the syntax for the DEFINE TEMP-TABLE Progress language statement is relevant to buffer creation:

DEFINE [ [ NEW [ GLOBAL ] ] SHARED ] TEMP-TABLE <temptable> [ NO-UNDO ]
...

where <temptable> is the name of the temp-table being defined, as well as the name of the default buffer which will be defined.

Example 5:

DEFINE TEMP-TABLE tmp-customer
...

Converted code:

TempRecord1 tmpCustomer = TemporaryBuffer.define(TempRecord1.class, "tmpCustomer", false);

Details:

This (partial) 4GL temp-table definition creates an implicit, default record buffer named tmp-customer.

The FWD conversion, in addition to defining a DMO interface and an implementation class in Java, and a Hibernate .hbm.xml mapping document, will declare and initialize a DMO as a member instance variable of the converted class. The tmpCustomer DMO is backed by an internally linked record buffer object.

The naming in this example again assumes default name conversion rules, and further assumes this is the first temp-table encountered during conversion.

Example 6:

Altering the statement slightly to create a new, shared temp-table, we would have:

DEFINE NEW SHARED TEMP-TABLE tmp-customer
...

Converted code:

TempRecord1 tmpCustomer = TemporaryBuffer.define(Customer.class, "tmpCustomer", false);

...

public void execute()
{
   externalProcedure(new Block()
   {
      public void body()
      {
         ...
         SharedVariableManager.addBuffer("tmpCustomer", tmpCustomer);
         ...
      }
   });
}

Details:

In addition to the schema artifacts associated with the defined temp-table, conversion defines an explicit, default buffer for the new, shared temp-table. It then shares the buffer via the SharedVariableManager, as in Example 3 above, to make it accessible to programs it presumably will invoke later.

Example 7:

DEFINE NEW GLOBAL SHARED TEMP-TABLE tmp-customer
...

Converted code:

TempRecord1 tmpCustomer = TemporaryBuffer.define(Customer.class, "tmpCustomer", true);

...

public void execute()
{
   externalProcedure(new Block()
   {
      public void body()
      {
         ...
         SharedVariableManager.addBuffer("tmpCustomer", tmpCustomer);
         ...
      }
   });
}

Details:

The only difference between this example and Example 6 is the use of the GLOBAL keyword, indicating that scope of the default buffer associated with this temp-table should survive until the end of the user's session. This difference is minor from a conversion standpoint; the only effect is that the last parameter to the TemporaryBuffer.define method in the converted output is true instead of false. Note that the code to share the buffer is identical to that in Example 6.

Example 8:

The 4GL code to access from a separate, external procedure the shared, default buffer created in Example 6 is:

DEFINE SHARED TEMP-TABLE tmp-customer
...

Likewise, the 4GL code to access from a separate, external procedure the shared, global buffer created in Example 7 is:

DEFINE GLOBAL SHARED TEMP-TABLE tmp-customer
...

Converted code:

Customer tmpCustomer = SharedVariableManager.lookupBuffer("tmpCustomer");

Details:

In either case, the converted output is the same, because we simply are looking up the shared, default buffer previously stored in the SharedVariableManager, not defining a new buffer. The tmpCustomer variable is declared as an instance member of the Java class which represents the converted, external procedure. It is initialized with the value returned by SharedVariableManager.lookup, using the buffer name assigned previously in the respective calling program's invocation of SharedVariableManager.addBuffer.

Record Buffers as Parameters

The 4GL procedures and functions can define a record buffer as parameter, beside variables. Depending on how a buffer is defined as a parameter, the associated parameter in the called procedure or function might be initialized to hold a reference to the passed record buffer, allowing the function or procedure to have access to the record currently loaded in the buffer.

When defining buffer parameters using the DEFINE PARAMETER BUFFER bufname FOR table (for procedures) and BUFFER bufname FOR table (for functions) statements, the resulting buffer will be:

  1. explicitly defined.
  2. assigned based on a parameter rather than causing a new memory allocation.
  3. scoped to the procedure or function to which they apply. This means that there will not be multiple scopes inside this block (for that buffer).

Buffers as Procedure Parameters

For procedures, buffer parameters can be defined using either the DEFINE PARAMETER BUFFER statement (which can define a parameter for a temporary or database table, always in input-output mode) or the DEFINE PARAMETER TABLE statement (which can be used to define parameters only for temporary tables).

The syntax of the DEFINE PARAMETER BUFFER statement is:

DEFINE PARAMETER BUFFER <buffername> FOR [TEMP-TABLE] <table> [PRESELECT].

This statement is used to create an alias for an existing buffer, which will be used within the procedure. When the procedure (internal or external) is executed, all changes to the buffer defined as parameter will reflect in the buffer instance which was passed to that parameter, if the procedure commits the transaction. For this statement, it is required for the passed buffer instance and the buffer parameter to be of the same type (i.e. they reference the same temporary or database table).

The syntax of the DEFINE PARAMETER TABLE statement is:

DEFINE <mode> PARAMETER TABLE FOR <temp_table_name> [table_options].

and is used to associated a temporary table with another one; it is not required for the passed buffer reference and the defined buffer parameter to be for the same temporary table - 4GL will match and use only the fields in the table which have the type and index. The mode can have one of the INPUT, OUTPUT or INPUT-OUTPUT values; if missing, it defaults to INPUT mode. The only supported value for table_options is APPEND. Depending on the mode, during initialization the destination table will have its records deleted (on input or input-output mode) or all created and changed records copied back to the table referenced by the passed buffer (on output or input-output mode).

Example 1:

...
procedure testProc1:
   define parameter buffer x-book-1 for book.
   ...
end.
...
run testProc1(buffer book).
...

Converted code:

...
Book book = RecordBuffer.define(Book.class, "p2j_test", "book");
Book xBook1;
...
public void testProc1(final Book xBook1)
{
  internalProcedure(new Block()
  {
      public void body()
      {
         RecordBuffer.openScope(xBook1);
         TestProc.this.xBook1 = xBook1;
         ...
      }
  });
}
...
testProc1(book);
...

Details:

This example adds to the procedure the buffer parameter xBook1, which maps to the database table book. In the converted code, beside emitting the procedure's buffer parameter, another buffer is emitted as an instance field for the converted external procedure. The instance field will be assigned to the buffer instance passed as parameter, when the internal procedure is executed; this is needed because the buffer parameter can end up being used in 4GL code which converts to inner classes, where the procedure's parameters are not visible; by “promoting” the parameter as an instance field, we expose the buffer instance to any other code which might need it.

The testProc1(book) call will invoke the testProc1 procedure, which needs a buffer parameter; the converted code will emit the converted buffer name, which in this case is book. This must be for the same database table as the procedure's parameter.

Example 2:

define temp-table tt-book like book.
...
procedure testProc2:
   define parameter buffer t-book-2 for temp-table tt-book.
   ...
end.
...
run testProc2(buffer tt-book).
...

Converted code:

...
TempRecord1 ttBook = TemporaryBuffer.define(TempRecord1.class, "ttBook", false);
TempRecord1 tBook2;
...
public void testProc2(final TempRecord1 tBook2)
{
  internalProcedure(new Block()
  {
      public void body()
      {
         RecordBuffer.openScope(tBook2);
         TestProc.this.tBook2 = tBook2;
         ...
      }
  });
}
...
testProc2(ttBook);
...

Details:

When a buffer for temporary table is passed as a parameter, the converted code is similar to the case when a buffer for a database table is used - the buffer instance is saved in a instance field for the converted external procedure.

Example 3:

define temp-table tt-book like book.
...
procedure testProc3:
   define temp-table tt-book-3 like book.

   define input parameter table for tt-book-3.
   ...
end.
...
run testProc3(input table tt-book).
...

Converted code:

...
TempRecord1 ttBook = TemporaryBuffer.define(TempRecord1.class, "ttBook", false);
TempRecord3 ttBook3 = TemporaryBuffer.define(TempRecord3.class, "ttBook3", false);
...
public void testProc3(final Temporary ttBook3Buf2)
{
  internalProcedure(new Block()
  {
      public void body()
      {
         TemporaryBuffer.associate(ttBook3Buf2, ttBook3, true, false);
         ...
      }
  });
}
...
testProc3(ttBook);
...

Details:

When using the DEFINE PARAMETER TABLE statement to define a parameter, any buffer instance associated with a temporary table can be passed for that parameter. Thus, the converted Java method needs to have the type of these kind of parameters (ttBook3Buf2 int this case) set to the generic Temporary type, which in FWD is the root interface for all temporary DMOs; this allows any temporary buffer to be passed as a buffer parameter. The buffer name (tt-book-3 in this case) needs to be explicitly defined before the DEFINE PARAMETER TABLE statement occurs. When using this statement, if none of the INPUT, OUTPUT or INPUT-OUTPUT options are specified, then it is assumed this is an INPUT parameter.

The ttBook3 buffer, which acts as a placeholder for the defined procedure parameter, is not the argument set to the converted Java method; instead, as in the DEFINE PARAMETER BUFFER case, an instance field (ttBook3) is used to define the temporary buffer for that temporary table. The reason is the same as for the DEFINE PARAMETER BUFFER statement - the parameter specified as a buffer can end up being used in inner classes in the converted code, which don't have access to the converted Java method's arguments.

The TemporaryBuffer.associate([src-buffer], [dst-buffer], [input], [output] {, [append] }) method is used to initiate the actual buffer which acts as the parameter, ttBook3; the arguments of this method are set depending on the options set to the@ DEFINE PARAMETER TABLE.The src-buffer stands for the buffer sent as a parameter ( ttBook3Buf2 in this case) and the dst-buffer references the buffer which acts as the procedure's parameter( ttBook3 ). The association between the arguments of this method and the statement's options and all the possible ways this method can be emitted in the converted code is shown in the table bellow. Note that the table-name buffer (the parameter) will always be emitted in place of the dst-buffer in the converted call.

4GL Code Converted Code
define input parameter
table for [table-name].
TemporaryBuffer.associate([src-buffer], [dst-buffer],
true, false);
define input-output parameter
table for [table-name].
TemporaryBuffer.associate([src-buffer], [dst-buffer],
true, true);
define output parameter
table for [table-name].
TemporaryBuffer.associate([src-buffer], [dst-buffer],
false, true);
define input parameter
table for [table-name] append.
TemporaryBuffer.associate([src-buffer], [dst-buffer],
true, false, true);
define input-output parameter
table for [table-name] append.
TemporaryBuffer.associate([src-buffer], [dst-buffer],
true, true, true);

Example 4:

...
define temp-table tt-book like book.
...
procedure testProc4:
   define temp-table tt-book-3 like book.

   define input-output parameter table for tt-book-4 append.
   ...
end.
...
run testProc4(input-output table tt-book).
...

Converted code:

...
TempRecord1 ttBook = TemporaryBuffer.define(TempRecord1.class, "ttBook", false);
TempRecord4 ttBook4 = TemporaryBuffer.define(TempRecord4.class, "ttBook4", false);
...
public void testProc4(final Temporary ttBook4Buf2)
{
  internalProcedure(new Block()
  {
      public void body()
      {
         TemporaryBuffer.associate(ttBook4Buf2, ttBook4, true, true, true);
         ...
      }
  });
}
...
testProc4(ttBook);
...

Details:

The APPEND clause can be specified only for OUPUT and INPUT-OUTPUT parameters. When it is set for a temporary buffer parameter, the T emporaryBuffer.associate method call will set its third boolean parameter to true. In this case, as it is an INPUT-OUTPUT parameter with an APPEND clause, all the boolean parameters for the TemporaryBuffer.associate method are set to true.

Example 5:

...
define parameter buffer x-book-a for book.
...

Converted code:

...
Book xBookA;
...
public void execute(final Book xBookA)
{
  externalProcedure(new Block()
  {
      public void body()
      {
         RecordBuffer.openScope(xBookA);
         TestProc.this.xBookA = xBookA;
         ...
      }
  });
}
...

Details:

Using DEFINE PARAMETER BUFFER with an external procedure results in a converted code similar to when defining a buffer parameter for an internal procedure: the buffer received as parameter (xBookA) will be set to an instance field for the converted external procedure (TestProc.xBookA).

Example 6:

...
define temp-table tt-book like book.
define parameter buffer t-book-a for temp-table tt-book.
...

Converted code:

...
TempRecord1 ttBook = TemporaryBuffer.define(TempRecord1.class, "ttBook", false);

TempRecord1 tBookA;
...
public void execute(final TempRecord1 tBookA)
{
  externalProcedure(new Block()
  {
      public void body()
      {
         RecordBuffer.openScope(tBookA, ttBook);
         TestProc.this.tBookA = tBookA;
         ...
      }
  });
}
...

Details:

This examples uses a buffer for a temporary table instead of a database table, but there are no differences in how the parameter for the external procedure is used: the parameter is received using the tBookA argument for the execute method and it is saved in the TestProc.tBookA instance field.

Example 7:

...
define temp-table tt-book like book.
define input parameter table for tt-book.
...

Converted code:

...
TempRecord1 ttBook = TemporaryBuffer.define(TempRecord1.class, "ttBook", false);
...
public void execute(final Temporary ttBookBuf1)
{
  externalProcedure(new Block()
  {
      public void body()
      {
         RecordBuffer.openScope(ttBook);
         TemporaryBuffer.associate(ttBookBuf1, ttBook, true, false);
         ...
      }
  });
}
...

Details:

Defining a parameter for the external procedure using the DEFINE PARAMETER TABLE results in a converted code similar to the case when this statement is used to define a parameter for an internal procedure. A temporary buffer of undefined type (ttBookBuf1) is used to receive the buffer parameter when invoking the external procedure and the TemporaryBuffer.associate method call will be used to initiate the actual temporary buffer parameter (ttBook). The TemporaryBuffer.associate is emitted using the same rules as for the DEFINE PARAMETER BUFFER statement.

h4(#buffers-as-function-parameters){font-style: normal}. Buffers as Function Parameters

For functions, the parameters are specified using special clauses in their parameter list: the BUFFER [buf-name] FOR [table-name] clause is used to specify a parameter for a database table and works in the same way as the DEFINE PARAMETER BUFFER statement works with a procedure; the TABLE FOR [table-name] is used to specify a parameter for a temporary table and is converted following the same rules as the DEFINE PARAMETER TABLE statement, when used with a procedure. Note that the BUFFER FOR clause can't be used in the BUFFER [buf-name] FOR TEMP-TABLE [table-name] form, and can be used only for specifying buffer parameters for database tables.

Example 1:

...
function testFun1 returns char (buffer x-book for book):
  ...
  return “foo”.
End.
...
message testFun1(buffer book).
...

Converted code:

...
Book book = RecordBuffer.define(Book.class, "p2j_test", "book");
Book xBook;
...
public character testFun1(final Book xBook)
{
  return characterFunction(new Block()
  {
      public void init()
      {
         FieldRef4b2.this.xBook = xBook;
      }

      public void body()
      {
         RecordBuffer.openScope(xBook);
         ...
         returnNormal("bar");
      }
  });
}
...
testFun1(book);
...

Details:

Although the function parameters are defined using different constructs, the converted parameter looks the same as when is set to a procedure: for the BUFFER ... FOR case, an “external procedure” instance field is created which will be set to the passed buffer reference. The reason is the same: the buffer parameter may end up being used in inner classes called by the function, which don't have access to the function's arguments.

Example 2:

...
def temp-table tt-book-2 like book.
...
function testFun2 returns char (input-output table for tt-book-2 append):
  ...
  return "bar".
end.
...
message testFun2(input-output table tt-book).
...

Converted code:

...
TempRecord1 ttBook = TemporaryBuffer.define(TempRecord1.class, "ttBook", false);
TempRecord8 ttBook2 = TemporaryBuffer.define(TempRecord8.class, "ttBook2", false);
...
public character testFun2(final Temporary ttBook2Buf2)
{
  return characterFunction(new Block()
  {
      public void init()
      {
         TemporaryBuffer.associate(ttBook2Buf2, ttBook2, true, true, true);
      }

      public void body()
      {
         returnNormal("bar");
      }
  });
}
...
testFun2(ttBook);
...

Details:

When a buffer for a temporary table is defined as parameter to a function, the converted code follows the same rules as for a procedure with a parameter defined using the DEFINE PARAMETER TABLE statement. This includes the TemporaryBuffer.associate method call - it is emitted to associate the received buffer instance with the buffer defined as parameter and initiate the defined parameter accordingly, depending on the buffer mode (INPUT, OUTPUT or INPUT-OUTPUT) and the APPEND state.

Record Scopes

A record scope is the beginning and ending of a buffer (associated with the table or "record" in question) that can be used to store real rows of data. This scope determines many things such as when data is written to the database or when a new record is fetched. However, from the parser's perspective, the only reason why record scope matters is because it affects unqualified field name resolution.

Progress databases are very likely to have a large number of field names that are duplicated between tables. For this reason, these field names are not unique across the database and thus in normal circumstances, they can only be unambiguously referenced when they are qualified. Wherever there are long lists of field names, it is convenient to allow the more liberal usage of unqualified names. This is possible whenever there are one or more record scopes that are active. In this case, the list of unique unqualified fields can be expanded based on the list of those tables in scope at the time. If multiple tables are in scope, then the set of all field names that are unique across that list of tables in scope will be available for searching even if those names would normally be unavailable due to conflicts with other tables. As long as the conflicts are only with tables that are not in scope, those unqualified names can be used. If however, the same field name is used in 2 or more of the tables that are in scope, that name still needs to be qualified on lookup.

Buffers can only be scoped to an enclosing block which has the record scoping property. All block types (external procedures, internal procedures, triggers, functions, repeat and for) except DO have the record scoping property by default. Even a DO obtains record scoping when either the FOR or PRESELECT qualifier is added (these are mutually exclusive).

Internal procedures, triggers and functions additionally have namespace scoping properties such that an explicit definition of a buffer name in one of these blocks will hide the same explicit buffer name in the containing external procedure. However, if no "local" definition exists for an explicit buffer name, references in these contained blocks will cause the original definition in the external procedure to be scoped to the external procedure level itself. Please note that any explicit buffer definitions inside an internal procedure, trigger or function cannot be used outside of that block. The external procedure is different in this respect (this is the same behavior as Progress provides for variables).

If a buffer is not explicitly created, then it is created implicitly via a direct table name reference in the source code. For example, if a table named customer exists in the currently connected database, then any of the following language constructs that reference customer (e.g. CREATE@customer@) cause a buffer of the same name (customer) to be created. This can be confusing since some language constructs use this same name to reference the schema definition of that table, some use the name to reference the table and some use the name as a buffer. This polymorphic usage is considered an "implicit" buffer.

Whether or not a language construct creates a buffer is determined by the type of the reference. The following maps language constructs to the type of record reference:

Language Construct Type Reference Type
any field reference in an expression lvalue free reference
AMBIGUOUS record built-in function free reference
AVAILABLE record built-in function free reference
ASSIGN field = expression statement free reference
ASSIGN record statement free reference
BUFFER-COMPARE buffer TO buffer statement free reference
BUFFER-COPY buffer TO buffer statement free reference
CAN-FIND( record ... ) built-in function no reference
CAN-FIND( ... OF record ) built-in function free reference
CREATE record statement free reference
CURRENT-CHANGED record built-in function free reference
DEFINE BROWSE browse_name QUERY query_name DISPLAY record WITH ... statement free reference
DEFINE BUFFER buffer-name FOR record statement no reference
DEFINE FRAME frame-name record statement no reference
DEFINE PARAMETER BUFFER buffername FOR record statement free reference
DEFINE PARAMETER TABLE FOR temp-table-name statement no reference
DEFINE type PARAMETER TABLE-HANDLE FOR temp-table-handle statement unknown
DEFINE TEMP-TABLE temp-table-name LIKE record statement no reference
DEFINE QUERY <queryname> FOR <tablename> statement free reference
DELETE record statement free reference
DISPLAY record statement free reference
DO FOR record statement strong
DO PRESELECT record statement weak
EMPTY TEMP-TABLE record statement free reference
EXPORT record statement free reference
FIND ... record statement free reference
FOR EACH record statement weak
FOR FIRST record statement weak
FOR LAST record statement weak
FORM record statement no reference
FUNCTION func_name RETURNS type (BUFFER buffername FOR record, ...). statement free reference
FUNCTION func_name RETURNS type (TABLE FOR temp_table_name, ...). statement no reference
IMPORT record statement free reference
INSERT record statement free reference
LOCKED record built-in function free reference
NEW record built-in function free reference
ON dbevent OF record statement free reference
OPEN QUERY query ... record ... statement free reference
PROMPT-FOR record statement free reference
RAW-TRANSFER buffer TO buffer statement free reference
RECID record built-in function free reference
RELEASE record statement free reference
REPEAT FOR record statement strong
REPEAT PRESELECT record statement weak
ROWID record built-in function free reference
RUN procedure (BUFFER record, ...) statement free reference
SET record statement free reference
UPDATE record statement free reference
VALIDATE record statement free reference

There are 4 types of record references (to database tables, temp-tables or work-tables) which may cause a buffer to be created. If this reference is implicit, the resulting buffer is named the same as the table to which it refers. It is important to note that even when a reference does create a buffer (allocate memory) only a small subset of the above statements actually fill it with a record (all buffers only hold a single record at a time). Other statements (e.g. FIND, FOR or GET) may handle both the creation of the buffer and the implicit loading of that buffer with the first record.

The following sections describe each of the reference types. 3 of the 4 types can cause the creation of a new buffer. Multiple references to the same buffer are sometimes combined into a single buffer creation and at other times more than 1 buffer can be created. The following describes the rules by which the number of buffers are determined. For each buffer that is created, there is also a "record scope" that corresponds to this buffer. This scope determines the topmost block in which the buffer is available.

In the following rules, the use of "implicit" should not be confused with whether or not a buffer name has been explicitly or implicitly referenced. In many cases, Progress will implicitly create a larger scope for a buffer such that multiple references share the same buffer. This is the sense in which the word implicit should be interpreted. Most importantly, the scoping rules (both the explicit rules and the implicit expansion rules) are applied in the same exact manner for both explicitly defined buffers as for implicitly defined buffers.

No Reference

Some name references can occur in many language statements, even before these scope creating statements. For example, a DEFINE TEMP-TABLE <temp-table-name> LIKE <table_name> statement is such a reference that can occur anywhere in a procedure. No buffer is being referenced here, instead it is the schema dictionary (the data structure of the record) that is being referenced.

Strong Reference

Only two language statements (both are kinds of block definitions) can be a strong reference (DO FOR and REPEAT FOR). A new buffer is created and that buffer is scoped to the block being defined. Strong scopes are explicitly defined by the 4GL programmer and the Progress environment never implicitly changes the scope to be larger (as can happen with the next two reference types). A strong reference doesn't ever implicitly expand beyond the block that defines it.

Other reference types can be nested inside a strong scope and the scope of these references is always raised to the strong scope. Thus every buffer reference within a strong scoped block is always scoped to the same buffer.

Additionally, no free references to the same buffer are allowed outside the block defining a strong scope (the code will fail to compile). Weak references to the same buffer can be placed outside or inside a strong scope.

Weak Reference

Only three language statements (all of which are kinds of block definitions) can be a weak reference (FOR EACH, DO PRESELECT and REPEAT PRESELECT). Depending on other references (or the lack thereof) in a file, a weak reference may create a new buffer and scope that buffer to the block in which the buffer was referenced.

If a weak reference to a buffer is nested inside a block to which the buffer is strongly scoped, then this reference does not create a new buffer and is simply a reference to that same strongly scoped buffer.

If a weak reference is not nested inside a strong scope, it will create a new buffer and scope that buffer to the block definition which made the weak reference, BUT this will only happen if no unbound free references to the same buffer exist (see Free Reference below for details).

With the existence of one or more unbound free references, it is possible that the weak reference will have its scope implicitly expanded to that of a containing scope. For this to occur, the scope of the buffer will be expanded to the nearest block that can enclose the multiple references to this same buffer. The problem is that there are complex rules to determine which references (weak and free) are combined into a single buffer (and its associated scope) and which weak references are left alone.

Free Reference

Anything that is not a "no reference", strong reference or weak reference is a free reference.

If the free reference is contained inside a block to which the referenced buffer is strongly scoped, then this is simply a reference to that same buffer and no buffer creation or scope change will occur.

If the free reference is contained inside a block to which the referenced buffer is weakly scoped, then this is simply a reference to that same buffer and no buffer creation or scope change will occur. Please note that the Progress documentation incorrectly states that "a weak-scope block cannot contain any free references to the same buffer". More specifically, one cannot nest certain free references to a given buffer that is scoped to a FOR EACH block (which is the most common type of weak reference). For example, one cannot use a FIND inside a FOR EACH where both are operating on the same buffer. However, one should note that field access is a form of free reference and this is perfectly valid (as one would expect) inside a FOR EACH.

Contrary to the Progress documentation, if the weak reference is created by a DO PRESELECT or REPEAT PRESELECT, then it is required that one use one or more free references in order to read records since neither of these blocks otherwise provide record reading. In this case, even FIND statements work within such a block.

If the free reference is not nested inside a block to which the same buffer is already scoped, then this free reference may cause the scope of other free references and weak references to be implicitly expanded to a higher containing block.

In two cases (FOR FIRST and FOR LAST), a free reference is part of a block definition. In such a case, if there is no need to implicitly expand the scope for this buffer, it will be scoped to the block definition in which the free reference was made.

Much more common is for a free reference to be a standalone language statement which is not part of a block definition (e.g. any form of FIND). In such cases, whether a buffer is created or not and where that buffer is scoped are decided via a complex series of rules.

It is important to note that in the absence of free references, there will never be any implicit expansion of record scopes. Thus the free reference is the cause of implicit scope expansion.

Implicit Scope Expansion Rules

The following are the rules for scope expansion (Progress does not document these rules, but they have been determined by experience). These rules all assume that all references refer to the same implicit buffer name. The previous rules for scoping are not repeated here as these rules only reflect the logic for implicitly expanding scope. To perform the analysis described here, one must traverse the Progress source AST that the parser creates with "forward" meaning down and/or right from the current node and "backward" meaning up and/or left of the current node. To understand some of the terms in the following rules, one must think of the tree as a list. In other words, one must "flatten" (degenerate) the tree into a simple list (based on the depth first approach of traversing down the tree before traversing right). Using this conceptual list form of the tree, one can see that the "closest prior" (the most recently encountered node that matches some criteria) or "next" (the closest downstream node that matches some criteria) node can be thought of as a simple backward or forward "search" respectively. This is critical to understand these rules since much of the logic is not defined by the nesting of blocks (as would be expected) but is rather defined by the "simple" ordering of the references.

  1. No scope is ever expanded without the presence of more than 1 reference to the same buffer where at least one of the references is a free reference.
  2. Any free or weak reference that is nested inside a weak or strong scope per the above rules, is not considered in this analysis. Such references are effectively ignored.
  3. Once (due to a match with one of the following rules) an implicit scope has been assigned (expanded) to a block, this is considered an active scope. Since at least one of the references that triggered the expansion must have been a free reference, this reference is now considered bound and no longer affects any further implicit expansion decisions.
  4. The block to which a scope expansion has occurred will be called an expanded scope in subsequent rules.
  5. Weak and free references that are positioned later than statements triggering the scope expansion but which are nested inside an active scope will use that active scope and will not affect or cause any further expansions. Such free references are considered bound free references.
  6. When an unbound free reference (any free reference that is not defined as bound as noted above) is encountered, an implicit scope expansion will occur. Unbound free references are greedy since they will "reuse" or "borrow" the buffer defined elsewhere if at all possible. A backward "search" is made for the first match to any of the following (to which to "bind" the free reference):
    • The most recent prior weak reference (at the current or a higher nesting level).
    • The most recent prior expanded scope .
    • If a nested block is encountered, the first (in order of occurrence, no matter its sub-nesting dept) weak reference of that block.
  7. Whichever of these backward references is found first is the reference or scope that is used to determine the enclosing block that will be the target for the expansion. If no prior weak references or prior expanded scopes exist to which to bind, then the next weak reference after the unbound free reference will be the bind target. By definition, an expanded scope can only be found by searching backward since the source tree is processed top to bottom/left to right and it is this current processing which creates expanded scopes.
  8. Once a bind target is found, the nearest block that encloses both references (or the one free reference and the expanded scope) and which has the record scoping property will form the target of an expanded scope. This means that at a minimum, the two references (or the one free reference and the expanded scope) will both use a common buffer that is associated with the target block.
  9. A special case exists when there is 1 or more unbound free references and no weak references or expanded scopes to the same buffer. This means that the only references to this buffer are free references and there are no buffers to "borrow". Instead a buffer is created and scoped based only on the free reference(s). The result is that all of these free references will be combined into a single expanded scope at the nearest block that encloses all free references and which has the record scoping property.
  10. An expanded scope's block target can be very close to the free reference (the currently enclosing block) or it can be located at any enclosing block up to and including the external procedure block itself. The target is calculated as described in rule 8 above. No matter which block is the target it is possible that there are prior weak references and expanded scopes that are contained within the scope of the target block. The following sub-rules must be used to determine which of these weak references or expanded scopes will have their scope raised or rolled up to the new enclosing target block and which of them will retain their own independent scope. If conditions cause some of the references or expanded scopes to remain separate, then the result is a nested island which uses a separate buffer referencing the same table by the same name.
    • This processing must be done in a backwards walk of the tree with the results heavily based on block nesting.
    • Only weak scopes and expanded scopes will be encountered in such a backward walk, because the forward walk to this point has by its nature, already bound all free references into expanded scopes.
    • The rollup process starts with the first prior weak reference or expanded scope which was not the bind target (see rules 6 and 7 above) and which is directly enclosed by the target block. In other words, the starting point must be a weak reference or expanded scope that is a direct child node of the target block.
    • The rollup process ends at (and including) the closest prior expanded scope or when there are no more weak references or expanded scopes to rollup . This means that only 1 prior expanded scope will ever have its scope raised in a given rollup process. The other (counter-intuitive) result of this is that any direct children of the target block that are weak references or expanded scopes but which precede the closest prior expanded scope, are never rolled up. In other words, they become islands of unique scope nested inside a larger scope but remaining separate. The closest prior expanded scope thus forms a kind of firewall before which all previous scopes (within the same enclosing target block) are forever fixed. In other words, once this firewall has been created (just another name for an expanded scope), certain prior scopes can never be rolled up.
    • All weak references that are directly enclosed by the target block and are located between the start and end points of the rollup process, will have their scope raised to the target block. They are essentially "merged" with that scope. Weak references that are more deeply nested (i.e. they are not direct child nodes of the target block) are not merged, with the single exception noted in the next sub-rule (10f).
    • Besides weak references and expanded scopes that exist as direct child nodes of the target block, this rollup process can cause a counter-intuitive scope merger for weak references that are more deeply nested. This only happens if there is a block nested directly inside the target block AND there are no expanded scopes (at any level) in that nested block. In this case (where the nested block has only weak references), the first (in order of occurrence) weak reference in that nested block (even if that weak reference is deeply nested inside that nested block) will have its scope raised while all other following weak references in the nested block will remain islands. This is really a side effect of or related processing for how nested blocks are processed in rule 6 above.
    • This rollup of the first weak reference in a nested block does not stop the rollup process. This means that multiple directly nested blocks in a target scope will each have their first weak reference's scope raised (assuming that each nested block contains no expanded scope). If one of these blocks contains an expanded scope, then the sub-rule (10d) above takes precedence and the first weak reference DOES NOT have its scope raised.

Implicit Scope Expansion Examples

The following are examples to show both the simple/expected behavior as well as each of the more obscure (counter-intuitive) cases. In each example the following explains how to interpret the text:

  1. Ignore the fact that these examples are completely useless (mostly just infinite loops), they are illustrative only.
  2. Only the customer buffer is being used. All scoping decisions are relative to this buffer and decisions are always independent between buffers with different names (even if they refer to the same table).
  3. Any text that is left in the color black has no record scope assigned at all. That means that the buffer(s) shown are invalid at those locations.
  4. Non-black colors are used to show scopes. Since scopes are associated with an entire block, the whole block will be colored the same except in the case where there are nested islands of different scope. In such cases the islands will be distinguished using multiple colors.

Example 1:

message "hello".

find first customer.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      RecordBuffer.openScope(customer);
      message("hello");
      new FindQuery(customer, ...).first();
   }
});

Details:

This is the most simple case. There is 1 free reference and the buffer created is associated with the external procedure.

Example 2:

message "hello".

repeat:

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

Another simple case with a single free reference. The scope is assigned at the nearest enclosing block that holds all references (and which has the record scoping property). In this case, the scope is associated with the repeat block instead of the external procedure.

Example 3:

repeat:

   repeat:

      repeat:

         repeat:

            repeat:

               find first customer.

            end.

            repeat:

               repeat:

                  find first customer.

               end.

            end.

         end.

      end.

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void body()
         {
            repeat("loopLabel1", new Block()
            {
               public void body()
               {
                  repeat("loopLabel2", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel3", new Block()
                        {
                           public void init()
                           {
                              RecordBuffer.openScope(customer);
                           }

                           public void body()
                           {
                              repeat("loopLabel4", new Block()
                              {
                                 public void body()
                                 {
                                    new FindQuery(customer, ...).first();
                                 }
                              });

                              repeat("loopLabel5", new Block()
                              {
                                 public void body()
                                 {
                                    repeat("loopLabel6", new Block()
                                    {
                                       public void body()
                                       {
                                          new FindQuery(customer, ...).first();
                                       }
                                    });
                                 }
                              });
                           }
                        });
                     }
                  });
               }
            });
         }
      });
   }
});

Details:

In the absence of weak references, 2 or more free references will all combine into a single scope at the nearest enclosing block that holds all references (and which has the record scoping property).

Note that the level of nesting has no impact on this result.

Example 4:

do for customer:

   for each customer:

   end.

   find first customer.

end.

for each customer:

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      doBlock("blockLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel0", new Block() { ... });

            new FindQuery(customer, ).first();
         }
      });

      forEach("loopLabel1", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
            ...
         }
         ...
      });
   }
});

Details:

Here, the DO FOR block establishes a strong scope. All customer references within this block are all scoped to the block. In this example, the free reference is not considered unbound because it is within the strong scope. Also note that no free references external to the same buffer are allowed outside this strong scope.

The weak reference within the strong scope is part of that scope, but the weak reference outside of the strong scope has its own buffer/record scope.

No implicit scoping will occur, everything is fixed and explicit.

Example 5:

message "hello".

for each customer:

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      forEach("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
            ...
         }
         ...
      });
   }
});

Details:

This weak reference is the simple case where the buffer created is associated with the FOR block.

No implicit scoping changes will occur.

Example 6:

message "hello".

for each customer:

end.

for each customer:

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      forEach("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
            ...
         }
         ...
      });

      forEach("loopLabel1", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
            ...
         }
         ...
      });
   }
});

Details:

These 2 weak references each have their own scope where the buffer created is associated with each FOR block.

No implicit scoping changes will occur.

Example 7:

message "hello".

do preselect each customer:

   find first customer.

   find next customer.

end.

for each customer:

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      doBlock("blockLabel0", new Block()
      {
         PreselectQuery query0 = null;

         public void init()
         {
            RecordBuffer.openScope(customer);
            ...
         }

         public void body()
         {
            query0.first();
            query0.next();
         }
      });

      forEach("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
            ...
         }
         ...
      });
   }
});

Details:

This is an example of two weak scopes that are sequential. Please note that while it is not possible to have a free reference inside a FOR EACH block, the PRESELECT blocks must by their nature use free references to read records.

Example 8:

message "hello".

repeat:

   for each customer:

   end.

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block() { ... });

            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

This is the most simple type of implicit scope expansion. The default scope for the weak reference is raised to the enclosing REPEAT block due to the presence of the FIND free reference.

The REPEAT block in this case would be considered an expanded scope in the text above.

Example 9:

message "hello".

repeat:

   repeat:

      for each customer:

      end.

   end.

end.

find first customer.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      RecordBuffer.openScope(customer);
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void body()
         {
            repeat("loopLabel1", new Block()
            {
               public void body()
               {
                  forEach("loopLabel1", new Block() { ... });
               }
            });
         }
      });

      new FindQuery(customer, ...).first();
   }
});

Details:

This implicit scope expansion shows that the default scope for the weak reference is raised to the external procedure due to the presence and location of the FIND free reference. An important note is that the nesting level of the FOR EACH weak reference makes no difference with this behavior. Likewise, whether or not the FIND was deeply nested has no impact. The decision to implicitly expand scope is based on the presence of an unbound free reference and the order in which the buffer references occur.

Example 10:

message "hello".

repeat:

   find first customer.

   repeat:

      repeat:

         for each customer:

         end.

      end.

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("outer", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            new FindQuery(customer, ...).first();

            repeat("loopLabel0", new Block()
            {
               public void body()
               {
                  repeat("loopLabel1", new Block()
                  {
                     public void body()
                     {
                        forEach("loopLabel2", new Block() { ... });
                     }
                  });
               }
            });
         }
      });
   }
});

Details:

This implicit scope expansion shows that the default scope for the weak reference is raised to the REPEAT block even though it follows the FIND free reference. In this case, there is no prior weak reference or expanded scope to which to bind.

Example 11:

repeat:

   repeat:

      repeat:

         for each customer.

         end.

         repeat:

            repeat:

               find first customer.

            end.

            repeat:

               repeat:

                  find first customer.

               end.

            end.

         end.

      end.

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void body()
         {
            repeat("loopLabel1", new Block()
            {
               public void body()
               {
                  repeat("loopLabel2", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                     }

                     public void body()
                     {
                        forEach("loopLabel3", new Block() { ... });

                        repeat("loopLabel4", new Block()
                        {
                           public void body()
                           {
                              repeat("loopLabel5", new Block()
                              {
                                 public void body()
                                 {
                                    new FindQuery(customer, ...).first();
                                 }
                              });

                              repeat("loopLabel6", new Block()
                              {
                                 public void body()
                                 {
                                    repeat("loopLabel7", new Block()
                                    {
                                       public void body()
                                       {
                                          new FindQuery(customer, ...).first();
                                       }
                                    });
                                 }
                              });
                           }
                        });
                     }
                  });
               }
            });
         }
      });
   }
});

Details:

In this case when the first free reference is encountered there is a prior weak reference AND it is at a different (higher in this case) nesting level so that changes the target block chosen.

It is important to note that following free references that are enclosed in this active scope (see rules 3 and 5 above) are automatically scoped to this same level and do not have any further impact.

Example 12:

repeat:

   repeat:

      repeat:

         repeat:

            repeat:

               find first customer.

            end.

            repeat:

               repeat:

                  find first customer.

               end.

            end.

         end.

      end.

      for each customer.

      end.

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void body()
         {
            repeat("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  repeat("loopLabel2", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel3", new Block()
                        {
                           public void body()
                           {
                              repeat("loopLabel4", new Block()
                              {
                                 public void body()
                                 {
                                    new FindQuery(customer, ...).first();
                                 }
                              });

                              repeat("loopLabel5", new Block()
                              {
                                 public void body()
                                 {
                                    repeat("loopLabel6", new Block()
                                    {
                                       public void body()
                                       {
                                          new FindQuery(customer, ...).first();
                                       }
                                    });
                                 }
                              });
                           }
                        });
                     }
                  });

                  forEach("loopLabel7", new Block() { ... });
               }
            });
         }
      });
   }
});

Details:

As the tree is walked, when an unbound free reference is encountered (the first FIND), a backward search will be made. In this case, no weak references or expanded scopes are found. So the free reference will bind to the next weak reference encountered. This expands the scope to the 2nd level REPEAT block.

Example 13:

repeat:

   repeat:

      for each customer:

      end.

      find first customer.

   end.

   repeat:

      for each customer:

      end.

      find first customer.

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void body()
         {
            repeat("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  forEach("loopLabel2", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });

            repeat("loopLabel3", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  forEach("loopLabel4", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });
         }
      });
   }
});

Details:

An example of 2 expanded scopes that never combine. This separation is due to the nesting levels of the scopes.

Example 14:

repeat:

   repeat:

      for each customer:

      end.

      find first customer.

   end.

   repeat:

      for each customer:

      end.

      for each customer:

      end.

      repeat:

         repeat:

            for each customer:

            end.

         end.

      end.

      repeat:

         repeat:

            find first customer.

         end.

         find first customer.

      end.

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void body()
         {
            repeat("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  forEach("loopLabel2", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });

            repeat("loopLabel3", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  forEach("loopLabel4", new Block() { ... });

                  forEach("loopLabel5", new Block() { ... });

                  repeat("loopLabel6", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel7", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel8", new Block() { ... });
                           }
                        });
                     }
                  });

                  repeat("loopLabel9", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel10", new Block()
                        {
                           public void body()
                           {
                              new FindQuery(customer, ...).first();
                           }
                        });

                        new FindQuery(customer, ...).first();
                     }
                  });
               }
            });
         }
      });
   }
});

Details:

A more complex example that shows that nesting may stop two expanded scopes from combining at a higher level, but that no matter how deeply nested the original unbound free reference and the weak reference to which it binds, the rollup process stops at the end of the expanded scope. If no subsequent free references occur outside of the expanded scope, such that it is rolled up further, this condition holds.

Example 15:

repeat:

   repeat:

      for each customer:

      end.

      find first customer.

   end.

   repeat:

      for each customer:

      end.

      for each customer:

      end.

      repeat:

         repeat:

            for each customer:

            end.

         end.

      end.

      repeat:

         repeat:

            find first customer.

         end.

         find first customer.

      end.

   end.

end.

find first customer.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      RecordBuffer.openScope(customer);

      repeat("loopLabel0", new Block()
      {
         public void body()
         {
            repeat("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  forEach("loopLabel2", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });

            repeat("loopLabel3", new Block()
            {
               public void body()
               {
                  forEach("loopLabel4", new Block() { ... });

                  forEach("loopLabel5", new Block() { ... });

                  repeat("loopLabel6", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel7", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel8", new Block() { ... });
                           }
                        });
                     }
                  });

                  repeat("loopLabel9", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel10", new Block()
                        {
                           public void body()
                           {
                              new FindQuery(customer, ...).first();
                           }
                        });

                        new FindQuery(customer, ...).first();
                     }
                  });
               }
            });
         }
      });

      new FindQuery(customer, ...).first();
   }
});

Details:

A more complex example that shows that nesting may stop two expanded scopes from combining at a higher level, even when there are subsequent free references that occur outside of the parent block of both expanded scope*s, such that the last one (forming a *firewall) it is rolled up further while the first one is left separate.

Example 16:

message "hello".

repeat:

   repeat:

      for each customer:

      end.

      find first customer.

   end.

   repeat:

      find first customer.

   end.

   repeat:

      for each customer:

      end.

      for each customer:

      end.

      repeat:

         repeat:

            for each customer:

            end.

         end.

      end.

      repeat:

         repeat:

            find first customer.

         end.

         find first customer.

      end.

   end.

end.

Converted code:

externalProcedure(new Block()
{
   message("hello");
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            repeat("loopLabel1", new Block()
            {
               public void body()
               {
                  forEach("loopLabel2", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });

            repeat("loopLabel3", new Block()
            {
               public void body()
               {
                  new FindQuery(customer, ...).first();
               }
            });

            repeat("loopLabel4", new Block()
            {
               public void body()
               {
                  forEach("loopLabel5", new Block() { ... });

                  forEach("loopLabel6", new Block() { ... });

                  repeat("loopLabel7", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel8", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel9", new Block() { ... });
                           }
                        });
                     }
                  });

                  repeat("loopLabel10", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel11", new Block()
                        {
                           public void body()
                           {
                              new FindQuery(customer, ...).first();
                           }
                        });

                        new FindQuery(customer, ...).first();
                     }
                  });
               }
            });
         }
      });
   }
});

Details:

The introduction of an intermediate free reference between what would have been 2 separate expanded scopes causes the first expanded scope to be raised up (as the bind target) and the entire contents of the 4th REPEAT block automatically have their scope raised because they are inside of an active scope.

Example 17:

repeat:

   repeat:

      repeat:

         repeat:

            repeat:

               find first customer.

            end.

            repeat:

               repeat:

                  find first customer.

               end.

            end.

         end.

      end.

      for each customer.

      end.

      for each customer.

      end.

      for each customer.

      end.

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void body()
         {
            repeat("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  repeat("loopLabel2", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel3", new Block()
                        {
                           public void body()
                           {
                              repeat("loopLabel4", new Block()
                              {
                                 public void body()
                                 {
                                    new FindQuery(customer, ...).first();
                                 }
                              });

                              repeat("loopLabel5", new Block()
                              {
                                 public void body()
                                 {
                                    repeat("loopLabel6", new Block()
                                    {
                                       public void body()
                                       {
                                          new FindQuery(customer, ...).first();
                                       }
                                    });
                                 }
                              });
                           }
                        });
                     }
                  });

                  forEach("loopLabel7", new Block() { ... });

                  forEach("loopLabel8", new Block() { ... });

                  forEach("loopLabel9", new Block() { ... });
               }
            });
         }
      });
   }
});

Details:

Another example of active scope processing. The number or order of subsequent enclosed references does not make any difference.

Example 18:

message "hello".

repeat:

   for each customer:

   end.

   for each customer:

   end.

   for each customer:

   end.

   repeat:

      repeat:

         for each customer:

         end.

      end.

      repeat:

         repeat:

            find first customer.

         end.

      end.

   end.

   for each customer:

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void body()
         {
            forEach("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            forEach("loopLabel2", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            forEach("loopLabel3", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            repeat("loopLabel4", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  repeat("loopLabel5", new Block()
                  {
                     public void body()
                     {
                        forEach("loopLabel6", new Block() { ... });
                     }
                  });

                  repeat("loopLabel7", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel8", new Block()
                        {
                           public void body()
                           {
                              new FindQuery(customer, ...).first();
                           }
                        });
                     }
                  });
               }
            });

            forEach("loopLabel9", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });
         }
      });
   }
});

Details:

This demonstrates the effect that nesting has (and doesn't have) on implicit scope expansion. All of the weak references in the top level REPEAT block never have their scope expanded because there is no unbound free reference at a nesting level that can affect them. However, the nested weak reference inside the 3rd level REPEAT block is the bind target for the single FIND free reference. The 2nd level REPEAT block is the nearest enclosing scope that can contain both weak and free references AND which has record scoping properties (see the next example). So the resulting expanded scope is never expanded again due to no additional unbound free references.

It is important to note that the relative nesting of the weak and free references within the 2nd level REPEAT block only affects the target block to which the scope is expanded. The relative nesting of the weak and free references does NOT affect the choice of which weak reference is bound to the free reference! Rather, this is primarily a sequence issue (it is almost always determined by the order or occurrence). The only exception is noted in rules 6a and 10f above.

Example 19:

message "hello".

repeat:

   for each customer:

   end.

   for each customer:

   end.

   for each customer:

   end.

   do:

      repeat:

         for each customer:

         end.

      end.

      repeat:

         repeat:

            find first customer.

         end.

      end.

   end.

   for each customer:

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block() { ... });

            forEach("loopLabel2", new Block() { ... });

            forEach("loopLabel3", new Block() { ... });

            blockLabel0:
            {
               repeat("loopLabel4", new Block()
               {
                  public void body()
                  {
                     forEach("loopLabel5", new Block() { ... });
                  }
               });

               repeat("loopLabel6", new Block()
               {
                  public void body()
                  {
                     repeat("loopLabel7", new Block()
                     {
                        public void body()
                        {
                           new FindQuery(customer, ...).first();
                        }
                     });
                  }
               });
            }

            forEach("loopLabel8", new Block() { ... });
         }
      });
   }
});

Details:

The only difference between this example and the previous one is that the 2nd level REPEAT block has been changed to a DO block. Since the DO block has no record scoping property by default, this means that the nearest enclosing block which has this property is the top level REPEAT block. Once the scope expands to this level (due to the FIND free reference), the previous weak references that are direct children of the target REPEAT block have their scope expanded too. Once that expanded scope is active, the subsequent weak references (the last one in the file) is automatically expanded too.

Example 20:

message "hello".

repeat:

   for each customer:

   end.

   for each customer:

   end.

   for each customer:

   end.

   repeat:

      repeat:

         for each customer:

         end.

      end.

      repeat:

         repeat:

            find first customer.

         end.

      end.

   end.

   for each customer:

   end.

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            forEach("loopLabel2", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            forEach("loopLabel3", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            repeat("loopLabel4", new Block()
            {
               public void body()
               {
                  repeat("loopLabel5", new Block()
                  {
                     public void body()
                     {
                        forEach("loopLabel6", new Block() { ... });
                     }
                  });

                  repeat("loopLabel7", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel8", new Block()
                        {
                           public void body()
                           {
                              new FindQuery(customer, ...).first();
                           }
                        });
                     }
                  });
               }
            });

            forEach("loopLabel9", new Block() { ... });

            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

This is an example of the exception in rule 10f above. 2 expansions occur, in series. The first is due to the deeply nested FIND free reference. This expands scope to the 2nd level REPEAT block and binds to the 4th instance of FOR EACH. Then when the 2nd FIND free reference is encountered, it binds to the 5th FOR EACH, raises scope to the top-level REPEAT block and then searches backward to expand the scope of prior weak or expanded scopes. In this case it stops its search when it encounters the first expanded scope (this is an example of the firewall described in rule 10d above). This means that the previous weak scopes each remain separate and independent (islands) but enclosed within a larger scope of the same buffer name.

Example 21:

message "hello".

repeat:

   for each customer:

   end.

   repeat:

      for each customer:

      end.

      find first customer.

   end.

   for each customer:

   end.

   for each customer:

   end.

   repeat:

      repeat:

         for each customer:

         end.

      end.

      repeat:

         repeat:

            find first customer.

         end.

      end.

   end.

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            repeat("loopLabel2", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  forEach("loopLabel3", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });

            forEach("loopLabel4", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            forEach("loopLabel5", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            repeat("loopLabel6", new Block()
            {
               public void body()
               {
                  repeat("loopLabel7", new Block()
                  {
                     public void body()
                     {
                        forEach("loopLabel8", new Block() { ... });
                     }
                  });

                  repeat("loopLabel9", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel10", new Block()
                        {
                           public void body()
                           {
                              new FindQuery(customer, ...).first();
                           }
                        });
                     }
                  });
               }
            });

            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

This demonstrates that a free reference can bind to the closest prior expanded scope. That expanded scope also performs the firewall function, causing all prior weak references and expanded scopes at that level to remain scoped as they are (no expansion of their scope to a higher level).

Example 22:

message "hello".

repeat:

   for each customer:

   end.

   repeat:

      for each customer:

      end.

      find first customer.

   end.

   repeat:

      for each customer:

      end.

      for each customer:

      end.

      repeat:

         repeat:

            for each customer:

            end.

         end.

         repeat:

            repeat:

               find first customer.

            end.

         end.

      end.

   end.

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            repeat("loopLabel2", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  forEach("loopLabel3", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });

            repeat("loopLabel4", new Block()
            {
               public void body()
               {
                  forEach("loopLabel5", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });

                  forEach("loopLabel6", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });

                  repeat("loopLabel7", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel8", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel9", new Block() { ... });
                           }
                        });

                        repeat("loopLabel10", new Block()
                        {
                           public void body()
                           {
                              repeat("loopLabel11", new Block()
                              {
                                 public void body()
                                 {
                                    new FindQuery(customer, ...).first();
                                 }
                              });
                           }
                        });
                     }
                  });
               }
            });

            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

This demonstrates that the closest prior expanded scope can be inside a nested block. That expanded scope still performs the firewall function, causing all prior weak references and expanded scopes at that level to remain scoped as they are (no expansion of their scope to a higher level).

In the backwards search, the prior expanded scope is found before the weak reference (the 4th FOR EACH), which is why the prior expanded scope is the bind target for the last FIND free reference and why the nested FOR EACH weak references are their own independent scopes.

Example 23:

message "hello".

repeat:

   for each customer:

   end.

   repeat:

      for each customer:

      end.

      find first customer.

   end.

   repeat:

      for each customer:

      end.

      for each customer:

      end.

      repeat:

         repeat:

            for each customer:

            end.

         end.

         repeat:

            repeat:

               find first customer.

            end.

         end.

      end.

      for each customer:

      end.

   end.

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            repeat("loopLabel2", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
               }

               public void body()
               {
                  forEach("loopLabel3", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });

            repeat("loopLabel4", new Block()
            {
               public void body()
               {
                  forEach("loopLabel5", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });

                  forEach("loopLabel6", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });

                  repeat("loopLabel7", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel8", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel9", new Block() { ... });
                           }
                        });

                        repeat("loopLabel10", new Block()
                        {
                           public void body()
                           {
                              repeat("loopLabel11", new Block()
                              {
                                 public void body()
                                 {
                                    new FindQuery(customer, ...).first();
                                 }
                              });
                           }
                        });
                     }
                  });

                  forEach("loopLabel12", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });
               }
            });

            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

The closest prior weak reference in this case is the bind target for the final free reference. However we can still see that the rollup process forces the closest prior expanded scope to be raised and that expanded scope performs the firewall function. Thus all prior weak references and expanded scopes in the nested 2nd level REPEAT block AND at the 1st level REPEAT block level to remain scoped as they are (no expansion of their scope to a higher level).

In the backwards search, the nested prior expanded scope is found as the first bind target. This is because a nested weak reference (except for the rule 10f above) cannot be a bind target.

Example 24:

repeat:

   for each customer:

   end.

   repeat:

      for each customer:

      end.

      find first customer.

   end.

   for each customer:

   end.

   for each customer:

   end.

   repeat:

      for each customer:

      end.

      for each customer:

      end.

      for each customer:

      end.

      repeat:

         repeat:

            for each customer:

            end.

         end.

      end.

   end.

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            repeat("loopLabel2", new Block()
            {
               public void body()
               {
                  forEach("loopLabel3", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });

            forEach("loopLabel4", new Block() { ... });

            forEach("loopLabel5", new Block() { ... });

            repeat("loopLabel6", new Block()
            {
               public void body()
               {
                  forEach("loopLabel7", new Block() { ... });

                  forEach("loopLabel8", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });

                  forEach("loopLabel9", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });

                  repeat("loopLabel10", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel11", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel12", new Block()
                              {
                                 public void init()
                                 {
                                    RecordBuffer.openScope(customer);
                                    ...
                                 }
                                 ...
                              });
                           }
                        });
                     }
                  });
               }
            });

            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

This shows that the last free reference binds to the first weak reference in a nested block (the 3rd REPEAT block, see rule 6c above). But also note that the all prior weak references and the closest prior expanded scope also get rolled up. That expanded scope forms a firewall which leaves the 1st FOR EACH as an island.

All weak references in the nested block (the 3rd REPEAT block) after the first one, are not _rolled up.

_The key points here are:

  1. The bind target is selected from 3 possible choices per rule 6 above (prior weak references at the same nesting level, the presence at any nesting level of an expanded scope or the first weak reference in a nested block). The first one encountered is chosen.
  2. Once the bind target is found, the rollup process extends from the point of the bind target until all prior direct child weak references (or first weak references in a nested block) are rolled up OR until the closest prior expanded scope is rolled up (the firewall).

Example 25:

repeat:

   for each customer.

   end.

   for each customer.

   end.

   repeat:

      repeat:

         for each customer.

         end.

         repeat:

            for each customer.

            end.

            find first customer.

         end.

         repeat:

            for each customer.

            end.

            find first customer.

         end.

         repeat:

            for each customer.

            end.

            find first customer.

         end.

         for each customer.

         end.

      end.

      for each customer.

      end.

   end.

   for each customer.

   end.

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            forEach("loopLabel2", new Block()
            {
               public void init()
               {
                  RecordBuffer.openScope(customer);
                  ...
               }
               ...
            });

            repeat("loopLabel3", new Block()
            {
               public void body()
               {
                  repeat("loopLabel4", new Block()
                  {
                     public void body()
                     {
                        forEach("loopLabel5", new Block()
                        {
                           public void init()
                           {
                              RecordBuffer.openScope(customer);
                              ...
                           }
                           ...
                        });

                        repeat("loopLabel6", new Block()
                        {
                           public void init()
                           {
                              RecordBuffer.openScope(customer);
                           }

                           public void body()
                           {
                              forEach("loopLabel7", new Block() { ... });

                              new FindQuery(customer, ...).first();
                           }
                        });

                        repeat("loopLabel8", new Block()
                        {
                           public void init()
                           {
                              RecordBuffer.openScope(customer);
                           }

                           public void body()
                           {
                              forEach("loopLabel9", new Block() { ... });

                              new FindQuery(customer, ...).first();
                           }
                        });

                        repeat("loopLabel10", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel11", new Block() { ... });

                              new FindQuery(customer, ...).first();
                           }
                        });

                        forEach("loopLabel12", new Block()
                        {
                           public void init()
                           {
                              RecordBuffer.openScope(customer);
                              ...
                           }
                           ...
                        });
                     }
                  });

                  forEach("loopLabel13", new Block()
                  {

                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });
               }
            });

            forEach("loopLabel14", new Block() { ... });

            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

Another example of how the rollup process stops processing when it encounters the closest prior expanded scope, even if this is deeply nested in sub-blocks.

Example 26:

message "hello".

repeat:

   for each customer:

   end.

   find first customer.

   repeat:

      for each customer:

      end.

      find first customer.

   end.

   for each customer:

   end.

   for each customer:

   end.

   repeat:

      repeat:

         for each customer:

         end.

      end.

      repeat:

         repeat:

            find first customer.

         end.

      end.

   end.

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block() { ... });

            new FindQuery(customer, ...).first();

            repeat("loopLabel2", new Block()
            {
               public void body()
               {
                  forEach("loopLabel3", new Block() { ... });

                  new FindQuery(customer, ...).first();
               }
            });

            forEach("loopLabel4", new Block() { ... });

            forEach("loopLabel5", new Block() { ... });

            repeat("loopLabel6", new Block()
            {
               public void body()
               {
                  repeat("loopLabel7", new Block()
                  {
                     public void body()
                     {
                        forEach("loopLabel8", new Block() { ... });
                     }
                  });

                  repeat("loopLabel9", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel10", new Block()
                        {
                           public void body()
                           {
                              new FindQuery(customer, ...).first();
                           }
                        });
                     }
                  });
               }
            });

            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

The introduction of a free reference early (the first FIND on line 8) in a program which causes the scope to be expanded early to the top-level REPEAT. All subsequent references within this top-level REPEAT block will scope to this level no matter the order or sub-nesting depth. This is an example of the effect of an active scope as described by rules 3 and 5 above.

Note that without this early free reference, the firewall effect will apply, as seen in the previous example.

Example 27:

message "hello".

repeat:

   for each customer:

   end.

   find first customer.

   repeat:

      for each customer:

      end.

   end.

   for each customer:

   end.

   for each customer:

   end.

   repeat:

      repeat:

         for each customer:

         end.

      end.

      for each customer:

      end.

   end.

   for each customer:

   end.

   for each customer:

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      message("hello");

      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block() { ... });

            new FindQuery(customer, ...).first();

            repeat("loopLabel2", new Block()
            {
               public void body()
               {
                  forEach("loopLabel3", new Block() { ... });
               }
            });

            forEach("loopLabel4", new Block() { ... });

            forEach("loopLabel5", new Block() { ... });

            repeat("loopLabel6", new Block()
            {
               public void body()
               {
                  repeat("loopLabel7", new Block()
                  {
                     public void body()
                     {
                        forEach("loopLabel8", new Block() { ... });
                     }
                  });

                  forEach("loopLabel9", new Block() { ... });
               }
            });

            forEach("loopLabel10", new Block() { ... });

            forEach("loopLabel11", new Block() { ... });
         }
      });
   }
});

Details:

Another example of the active scope effect. Note that in this case only a single free reference exists in the entire program, but the result is the same. Also note that the sub-nesting level has no impact on whether the contained weak references (or expanded scopes if any had existed in this example) would have their scope expanded.

Example 28:

repeat:

   for each customer.

   end.

   for each customer.

   end.

   repeat:

      repeat:

         repeat:

            for each customer.

            end.

         end.

         for each customer.

         end.

         repeat:

            for each customer.

            end.

         end.

         repeat:

            for each customer.

            end.

         end.

         for each customer.

         end.

      end.

      for each customer.

      end.

   end.

   for each customer.

   end.

   repeat:

      for each customer.

      end.

      for each customer.

      end.

   end.

   for each customer.

   end.

   find first customer.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block() { ... });

            forEach("loopLabel2", new Block() { ... });

            repeat("loopLabel3", new Block()
            {
               public void body()
               {
                  repeat("loopLabel4", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel5", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel6", new Block() { ... });
                           }
                        });

                        forEach("loopLabel7", new Block()
                        {
                           public void init()
                           {
                              RecordBuffer.openScope(customer);
                              ...
                           }
                           ...
                        });

                        repeat("loopLabel8", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel9", new Block()
                              {
                                 public void init()
                                 {
                                    RecordBuffer.openScope(customer);
                                    ...
                                 }
                                 ...
                              });
                           }
                        });

                        repeat("loopLabel10", new Block()
                        {
                           public void body()
                           {
                              forEach("loopLabel11", new Block()
                              {
                                 public void init()
                                 {
                                    RecordBuffer.openScope(customer);
                                    ...
                                 }
                                 ...
                              });
                           }
                        });

                        forEach("loopLabel12", new Block()
                        {
                           public void init()
                           {
                              RecordBuffer.openScope(customer);
                              ...
                           }
                           ...
                        });
                     }
                  });

                  forEach("loopLabel13", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });
               }
            });

            forEach("loopLabel14", new Block() { ... });

            repeat("loopLabel15", new Block()
            {
               public void body()
               {
                  forEach("loopLabel16", new Block() { ... });

                  forEach("loopLabel17", new Block()
                  {
                     public void init()
                     {
                        RecordBuffer.openScope(customer);
                        ...
                     }
                     ...
                  });
               }
            });

            forEach("loopLabel18", new Block() { ... });

            new FindQuery(customer, ...).first();
         }
      });
   }
});

Details:

This is an important example of the rollup process. There is only a single free reference (the FIND statement at the end) and it binds to the FOR EACH just prior to it. The rollup process occurs from there, and since there is no prior expanded scope to act as a firewall, the rollup continues all the way up to the top of the enclosing top-level REPEAT. Both direct child weak references and the first occurring weak references in 2 nested blocks are rolled up. Note that due to nesting 6 weak scopes do NOT get rolled up.

Example 29:

repeat:

   for each customer:

   end.

   for each customer:

   end.

   repeat:

      repeat:

         repeat:

            find first customer.

         end.

      end.

      repeat:

         for each customer:

         end.

      end.

   end.

   for each customer:

   end.

end.

Converted code:

externalProcedure(new Block()
{
   public void body()
   {
      repeat("loopLabel0", new Block()
      {
         public void init()
         {
            RecordBuffer.openScope(customer);
         }

         public void body()
         {
            forEach("loopLabel1", new Block() { ... });

            forEach("loopLabel2", new Block() { ... });

            repeat("loopLabel3", new Block()
            {
               public void body()
               {
                  repeat("loopLabel4", new Block()
                  {
                     public void body()
                     {
                        repeat("loopLabel5", new Block()
                        {
                           public void body()
                           {
                              new FindQuery(customer, ...).first();
                           }
                        });
                     }
                  });

                  repeat("loopLabel6", new Block()
                  {
                     public void body()
                     {
                        forEach("loopLabel7", new Block() { ... });
                     }
                  });
               }
            });

            forEach("loopLabel8", new Block() { ... });
         }
      });
   }
});

Details:

This helps demonstrate the sequential nature of the bind target decision. The 1st free reference is encountered deeply nested inside the 4th REPEAT block. It searches backward and binds to the 2nd FOR EACH (just inside the top-level REPEAT block). The rollup process then raises the scope for the 1st FOR EACH to the same top-level REPEAT block and ends. Now the last 2 weak references are both contained inside an active scope, so they automatically get their scope raised too.

If the FIND free reference and the 3rd FOR EACH were transposed in sequence, the result would have been 4 separate scopes, rather than 1 single scope. Note that nesting in this case has no affect, only the sequence mattered.

Nesting Scopes

In Progress, there are various restrictions when nesting two or more buffer scopes. This depends on the type of the nested scopes and whether the same buffer is used in multiple, nested scopes. When nesting two scopes referring to the same table, the restrictions are related to the type of the scopes: 2 weak scopes or 2 strong scopes can't be nested and also a strong scope cannot be tested inside a weak scope for the same buffer. On the other hand, it is possible to nest a weak scope inside a strong scope where both are referencing the same table. Note that if a weak scope is nested inside a strong scope, that weak scope cannot be expanded by a free reference - the strong scope binds to the specified block and cannot (by definition) ever be expanded.

Record scopes create a schema namespace issue because while a record is in scope, unqualified field names resolve which would normally be ambiguous. In these cases, the parser needs to disambiguate the unqualified field name and make sure the field will be associated with the correct buffer. For this, the subset of unique field names (that are unique across those tables actively in scope) are added to the namespace and any common (non-unique across the active scopes) names are removed from the namespace, each time a new nested scope is encountered. As tables are added and removed from the active scope list, each such change can both add and remove from the namespace, and adds to the complexity of the field name lookup algorithm. This is complicated by the fact that the algorithm to decide upon the block to which a given buffer is scoped cannot be implemented in a single-pass parser.

The primary difference between these scopes seems to be in how the scopes get implicitly expanded based on other database references in containing blocks. Normally a scope is limited to the nearest block in which the scope was created. Strong scopes can be removed from namespace consideration as soon as the this block ends. Weak scopes and free references can both be expanded to the containing block depending on a complex set of conditions. Note that within the naturally scoped block, normally ambiguous unqualified field names are made unambiguous. The problem is that the parser must properly detect when the scope is implicitly expanded and know when to remove the scope. The parser cannot simply leave all weak and free reference scopes in scope permanently. This is because in a complex procedure, as scopes are removed, some names are removed but some names may also be added back! This is due to the fact that adding a scope while other scopes are still active will remove any field names that are in common between all active scopes. So, removing that scope, re-adds those removed names to the namespace. Since the namespace can expand when a scope is removed, it is critical to detect this exact point and implement this removal otherwise the parser will fail to lookup names that should not be ambiguous.

The approach has been to use the SchemaDictionary namespace to keep track of scopes. At the start of every for, repeat or do block a scope is added and at the end, this scope is removed. This stack of scopes handles the nesting of names properly. Then the parser makes a very big simplifying assumption: every record (table, buffer, work-table or temp-table) reference is automatically "promoted" to the current scope (the top of the stack of scopes). This allows any tables in the current scope to take precedence for unqualified field lookups and then tables in the next scope down take precedence and so on until the name is resolved (remember: valid Progress code will ALWAYS have only unambiguous names so it is only a matter of where one finds it). The problem in this simplistic approach is that Progress may not always promote as we do. Thus in our implementation, there may be promoted tables that wouldn't otherwise be there. This may cause some names that would have been unambiguous (with a more limited set of tables in scope) to now be ambiguous. We don't have an example of such a situation but if it does occur, the implementation may have to get much more smart about when to and when not to promote.

It is very important to note that there is only ever 1 buffer (memory allocation) in each given procedure (internal or external), function or trigger, even though there may be multiple buffer scopes. The scopes affect record-release but the same memory is used for storage of records read in all contained scopes and Progress doesn't restore the state of the buffer when a nested scope is complete. This means that when a nested scope is added, the record that had been there is released at the end of that scope. Once that record is released, there is no record in the buffer. If there is downstream processing within the outer scope that expects the record to be there, it will find the record missing.

Unless a record is explicitly defined in an internal procedure, function or trigger, the buffer creation will occur at the external procedure level and this same buffer will be in use the entire scope of the external procedure. In the case where an internal procedure, function or trigger does have an explicit buffer defined, this results in namespace hiding and an additional (local) memory allocation. Buffer names can be the same with a buffer in the external procedure, but any explicit definition will create a local instance of that buffer. Nested scopes that operate on that buffer will have no affect on the buffer of the same name in an enclosing external procedure.

Multiple scopes are possible with explicitly defined buffers, just as they are with implicit buffers, even though there is only ever 1 memory allocation per procedure, trigger or function. The exception is for shared buffers that are imported (e.g. DEFINE SHARED BUFFER b FOR customer) rather than allocated locally. In this case, there is only ever a single scope for this buffer and that scope is set to the external procedure. Access to the shared buffer is imported once and is accessible for the lifetime of the procedure. In the cases of NEW SHARED and NEW GLOBAL SHARED buffers, these do cause a memory allocation and they can have multiple scopes just as any other explicit buffer or as the implicit buffers.

Following examples demonstrate how an explicit buffer defined in an internal procedure, function or trigger is scoped only to that internal procedure, function or trigger and they can't be used outside of that block.

Example 1:

procedure testProc1:
  define buffer x-book for book.
  find first x-book.
  return x-book.book-title.
end.

Converted code:

Book xBook = RecordBuffer.define(Book.class, "p2j_test", "xBook");
...
public void testProc1()
{
   internalProcedure(new Block()
   {
      public void body()
      {
         RecordBuffer.openScope(xBook);
         new FindQuery(xBook, (String) null, null, "xBook.bookId asc").first();
         returnNormal(xBook.getBookTitle());
      }
   });
}

Details:

The explicit buffer x-book is defined in the testProc1 internal procedure. As there is no other buffer defined with the same name in the external procedure or other function or internal procedure, the converted buffer name uses only the name as defined in the legacy 4GL code.

Example 2:

define buffer x-book for book.
find first x-book.
message x-book.book-title.
...
procedure testProc2:
  define buffer x-book for book.
  find first x-book.
  return x-book.book-title.
end.

Converted code:

Book xBook = RecordBuffer.define(Book.class, "p2j_test", "xBook");

Book xBookBuf2 = RecordBuffer.define(Book.class, "p2j_test", "xBookBuf2");

public void execute()
{
   externalProcedure(new Block()
   {
      public void body()
      {
         RecordBuffer.openScope(xBook);
         new FindQuery(xBook, (String) null, null, "xBook.bookId asc").first();
         message((character) new FieldReference(xBook, "bookTitle").getValue());
      }
   });
}

public void testProc2()
{
   internalProcedure(new Block()
   {
      public void body()
      {
         RecordBuffer.openScope(xBookBuf2);
         new FindQuery(xBookBuf2, (String) null, null, "xBookBuf2.bookId asc").first();
         returnNormal(xBookBuf2.getBookTitle());
      }
   });
}

Details:

In this case, although the x-book is used as a buffer name in both the external procedure and the internal procedure, they are actually two completely different buffers. In this case, to disambiguate between the two buffer names, the buffer defined in the internal procedure is added a counter suffix, resulting in the xBookBuf2 name.

Example 3:

function testFun1 returns char ():
  define buffer x-book for book.
  find first x-book.
  return x-book.book-title.
end.

Converted code:

Book xBook = RecordBuffer.define(Book.class, "p2j_test", "xBook");
...
public character testFun1()
{
   return characterFunction(new Block()
   {
      public void body()
      {
         RecordBuffer.openScope(xBook);
         new FindQuery(xBook, (String) null, null, "xBook.bookId asc").first();
         returnNormal(xBook.getBookTitle());
      }
   });
}

Details:

This example demonstrates how a buffer defined in a function gets converted to a distinct buffer, which can be accessed only from that function.

Example 4:

define buffer x-book for book.
find first x-book.
message x-book.book-title.

function testFun2 returns char ():
  define buffer x-book for book.
  find first x-book.
  return x-book.book-title.
end.

Converted code:

Book xBook = RecordBuffer.define(Book.class, "p2j_test", "xBook");

Book xBookBuf2 = RecordBuffer.define(Book.class, "p2j_test", "xBookBuf2");
...
public void execute()
{
   externalProcedure(new Block()
   {
      public void body()
      {
         RecordBuffer.openScope(xBook);
         new FindQuery(xBook, (String) null, null, "xBook.bookId asc").first();
         message((character) new FieldReference(xBook, "bookTitle").getValue());
      }
   });
}

public character testFun1()
{
   return characterFunction(new Block()
   {
      public void body()
      {
         RecordBuffer.openScope(xBookBuf2);
         new FindQuery(xBookBuf2, (String) null, null, "xBookBuf2.bookId asc").first();
         returnNormal(xBookBuf2.getBookTitle());
      }
   });
}

Details:

Similar to the internal function case, when the same name is used to define a buffer in the external procedure and a function, the conversion rules will disambiguate between the two cases by converting each buffer definition to an unique name.

Example 5:

on leave anywhere
do:
   define buffer x-book for book.
   find first x-book.
   message x-book.book-title.
end.

Converted code:

Book xBook = RecordBuffer.define(Book.class, "p2j_test", "xBook");

public class TriggerBlock0
extends Trigger
{
   public void body()
   {
      RecordBuffer.openScope(xBook);
      new FindQuery(xBook, (String) null, null, "xBook.bookId asc").first();
      message((character) new FieldReference(xBook, "bookTitle").getValue());
   }
}

Details:

If a buffer is explicitly defined within a trigger, then the buffer is isolated and can be used only in that trigger, similar to the internal procedure and function cases.

Example 6:

define buffer x-book for book.
find first x-book.
message x-book.book-title.

on leave anywhere
do:
   define buffer x-book for book.
   find first x-book.
   message x-book.book-title.
end.

Converted code:

Book xBook = RecordBuffer.define(Book.class, "p2j_test", "xBook");

Book xBookBuf2 = RecordBuffer.define(Book.class, "p2j_test", "xBookBuf2");

public void execute()
{
   externalProcedure(new Block()
   {
      public void body()
      {
         RecordBuffer.openScope(xBook);
         new FindQuery(xBook, (String) null, null, "xBook.bookId asc").first();
         message((character) new FieldReference(xBook, "bookTitle").getValue());
         ...
      }
   });
}

public class TriggerBlock0
extends Trigger
{
   public void body()
   {
      RecordBuffer.openScope(xBookBuf2);
      new FindQuery(xBookBuf2, (String) null, null, "xBookBuf2.bookId asc").first();
      message((character) new FieldReference(xBookBuf2, "bookTitle").getValue());
   }
}

Details:

Using the same name to define two buffers, one in the external procedure and one in a trigger block, it results in two completely different buffers. To disambiguate, the conversion rules will make sure the buffer definitions are converted to unique names.


© 2004-2017 Golden Code Development Corporation. ALL RIGHTS RESERVED.