Project

General

Profile

Database Field References

In 4GL, although a database field can be used in the same constructs as a normal 4GL variable, there are major differences in the converted code. Depending on the field type (case-(in)sensitive, extent, etc) and on the location where the field reference appears in the original 4GL code, the converted code which accesses or updates the database field will look different.

This chapter will show how the field references get converted, depending on their usage - in UI statements, for reading/writing, as parameters, etc. Beside the simple getter and setter accessors which are available via the generated DMO, in certain cases the converted code will wrap the access to the database fields using a FieldReference instance or an instance to one of the p2j.persist.types.*Field classes.

The FieldReference is emitted when the database field is used in the UI statements (like DISPLAY, MESSAGE, UPDATE, IMPORT, PUT, etc) and other special constructs where the reading of the field's value needs to be deferred. This class provides a readable, writable reference to a buffer field, used in cases where the field needs to be accessed at some arbitrary point in time after it is referenced in application code. For example, an instance of this class would be used as a substitution parameter in a CompoundQuery, when an inner loop where clause of a multi-table query refers to a field within a buffer retrieved in an outer loop. The read and write capability is provided for the backing field, insofar as the DMO has both an accessor (getter) and a mutator (setter) method defined for the specified property. Both indexed and non-indexed versions of these methods are supported.

The next table presents the FieldReference class, which has a major use in the converted code, especially when UI statements are involved; this table shows the most important constructors and APIs which might be emitted during conversion:

Constructor/Method Details
public FieldReference(DataModelObject dmo,
String property)
This is a constructor which takes the DMO object as the first parameter and the field name as the second parameter.
public FieldReference(DataModelObject dmo,
String property,
boolean uppercase)
This is another constructor which additionally takes a boolean third parameter; when true, this parameter will force the value returned by the getValue() call to be uppercased and right-trimmed, if this is a case-sensitive field.
This constructor is emitted only for case-sensitive character fields.
public FieldReference(DataModelObject dmo,
String property,
int index)
The third parameter in this constructor is the 0-based index of the element in an extent field.
This constructor is emitted only for extent fields.
public FieldReference(DataModelObject dmo,
String property,
NumberType index)
The third parameter in this constructor is the 0-based index of the element in an extent field, but is passed using a 4GL-style object, and not using a Java primitive value.
This constructor is emitted only for extent fields.
public FieldReference(DataModelObject dmo,
String property,
boolean uppercase,
int index)
This constructor brings together the two additional parameters which are present in the previous two constructors: the boolean parameter makes the getValue() call to uppercase and right-trim the value before returning it and the second integer parameter is the 0-based index for the extent field.
This constructor is emitted only for extent case-sensitive character fields.
public FieldReference(DataModelObject dmo,
String property,
boolean uppercase,
NumberType index)
This constructor is similar with the previous one, with one difference: the element index is passed using a 4GL-style object, and not using a Java primitive value.
This constructor is emitted only for extent case-sensitive character fields.
public BaseDataType getValue() This method is used to retrieve the value of the field currently referenced by the wrapped buffer. It gets emitted only when a buffer field is used in a MESSAGE statement.

The next section will show you how the property accessors (getter and setters ) are emitted when the field is mutated or read; this will be followed by sections which describe how the FieldReference construct gets emitted during conversion, when a field reference is encountered in UI (and other) statements. The last section of this chapter (Database Fields as Parameters) provides information about how the p2j.persist.types.*Field wrapper classes are emitted when the field is used as a function or procedure parameter.

Assigning and Reading Fields

In 4GL, record field changes and field access is done by specifying explicitly the buffer and field name; although 4GL allows the reference of certain field without specifying the buffer name (i.e. when the call is inside a FOR or DO FOR block), in all the examples in this section we will always prefix the field with its buffer name.

To be able to understand how the statements which access or mutates the buffer fields are emitted in the converted code, please go to the Data Model Object chapter and review how a certain table gets converted to Java code: what is the DMO interface, what are the getters and setters associated with a field and what are the special getters and setters used to access or set the data in an extent field. Once the Data Model Object chapter is reviewed, it should be clear that in the converted code, all field access is done using the getters defined in the DMO interface and all field changes are done using the setters defined in the DMO interface. Following are some examples about how these kind of statements get converted:

4GL Code Converted Code Notes
...
book.book-title = 'Conversion Reference'.
...
...
book.setBookTitle(new character('Conversion Reference'));
...
This is a simple assignment. The setter for the bookTitle field (as it appears defined in the DMO interface) is used to change the field value. Note that even if we assign it to a string constant, the converted code needs to wrap the string constant in a new 4GL-compatible character value; this is because the converted DMO interface doesn't expose setters which take a primitive Java type as a parameter.
def var t like book.book-title.
...
t = book.book-title.
...
...
t.assign(book.getBookTitle());
...
This is again a simple assignment, but instead of changing the field, we retrieve it and save it in a variable. Note that this time the getter for the bookTitle property is used to retrieve the field's value.
...
book.author-id[1] = 5.
...
...
book.setAuthorId(0, new integer(5));
...
In this case, we have a simple assignment of an extent field. Note that the first parameter of this special setter is the index of the element we need to change, which in the converted code is emitted as a 0-based index - so it will always be 1 less than the value in the original 4GL code.
def var h as int.
...
h = book.author-id[1].
...
...
h.assign(book.getAuthorId(0));
...
In this example the special getter of the extent field is used to retrieve the value on index 1 and set it to the local variable. Note that in this case too the converted code has all index references with 1 less than the value in the original 4GL code.
...
book.author-id = 99.
...
...
book.setAuthorId(99);
...
This is a special setter for the extent fields, which is used to set all the elements of the extent field to the specified value.
def var h like book.author-id.
...
h = book.author-id.
...
...
assignMulti(h, book.getAuthorId());
...
The assignMulti call is a method defined in the com.goldencode.p2j.util.ArrayAssigner class. Although the conversion rule emits this special getter which should return all the elements of the extent field as an array, note that this getter is not currently emitted in the DMO interface; so, this case will result in a compilation error, as the integer[] getAuthorId() method is not currently emitted in the book's DMO interface.

In 4GL, field usage is not limited in simple assignment statements. They can be used in any UI statement (like UPDATE, DISPLAY, MESSAGE, etc) which needs to update or only display a certain field. In this case, the converted code will not rely on the field's setters and getters; instead, the access to that field will be done using a special wrapper, represented by the com.goldencode.p2j.persist.FieldReference class. More details about how this class can be used will be provided in the subsequent sections.

Progress 4GL also provides the ASSIGN statement, which allows multiple fields to be assigned in batch, postponing the field and index validation until after the last assignment was executed. In the converted code, the generated DMO property assignment will follow the same rules as when the ASSIGN statement was not used (i.e. the property setter will be invoked to set its value). For more general details about this statement, please see the ASSIGN Statement section of the Data Types chapter of Part 4.

As with the CREATE and DELETE statement, using a buffer in an assignment which changes one or more of its fields will affect the transaction type of the enclosing block. More details on how and why this is done can be found in the Transactions chapter of Part 4.

Other cases when a simple getter is emitted is when a field is used in a complex expression, the expression being part of another statement (PUT, IMPORT, etc). Examples of such cases can be found in the subsequent sections.

Field References in UI statements

In this section, we will cover how the database fields get converted when used in UI statements. For ease of access, all the examples will be grouped by the statement type. The examples will describe only how the UI statements interact with the buffer fields; for details on how the UI statement and the frames get converted, please see the Frames chapter of this book. As previously noted, all UI statements will use either a FieldReference construct or a field accessor (getter or setter) to access or change the field's value.

UPDATE and DISPLAY statements

Example 1:

...
update book.book-title.
...

Converted code:

...
FrameElement[] elementList1 = new FrameElement[]
{
   new Element(new FieldReference(book, "bookTitle"), frame0.widgetBookTitle()),
};

frame0.update(elementList1);
...

Details:

In this example, note how the FieldReference constructor is used: the first parameter is the name of the instance field which references the buffer, while the second parameter is the field name as it appears in the converted DMO. The FWD runtime will automatically update the field value for the record referenced by the buffer, if the UPDATE statement completes normally. Note that the same FieldReference construct will be used if the UPDATE is replaced with the SET or PROMPT-FOR statement.

Example 2:

...
display book.book-title.
...

Converted code:

...
FrameElement[] elementList1 = new FrameElement[]
{
   new Element(new FieldReference(book, "bookTitle"), frame0.widgetBookTitle()),
};

frame0.display(elementList1);
...

Details:

This example has no difference in the buffer usage; the only difference is that the DISPLAY is used instead of UPDATE, which gets converted to a different API call; the field usage is the same as with the UPDATE statement.

Example 3:

...
display book.author-id[1].
...

Converted code:

...
FrameElement[] elementList0 = new FrameElement[]
{
    new Element(new FieldReference(book, "authorId", 0), frame0.widgetAuthorIdArray0())
};

frame0.display(elementList0);
...

Details:

Note that the FieldReference constructor now is emitted a third parameter; in this case, this parameter is element index from the 4GL code, converted from the 1-based to 0-based representation.

Example 4:

def var idx as int.
...
idx = 1.
display book.author-id[idx].
...

Converted code:

...
FrameElement[] elementList0 = new FrameElement[]
{
    new Element(new FieldReference(book, "authorId", minus(idx, 1)), frame0.widgetAuthorIdArray0())
};

frame0.display(elementList0);
...

Details:

When a variable is used to access the value at the given index for the extent field, the conversion process will automatically subtract 1 from the variable, to ensure the index will be 0-based.

Frame Properties

4GL allows dynamic frame properties, such as dynamic title, row, column and frame down. When the frame's title is set to a database field, FWD will access the field using a FieldReference construct. By wrapping the access to the database field using the FieldReference instance as an accessor ensures that each time the frame is displayed, the title is automatically computed using the current value held by the database field. The same approach is used for the frame's row, column and frame down properties.

Example 1:

...
display book.isbn book.publisher with title book.book-title.
...

Converted code:

...
frame0.openScope();
frame0.setDynamicTitle(new FieldReference(book, "bookTitle"));
...
FrameElement[] elementList1 = new FrameElement[]
{
  new Element(new FieldReference(book, "isbn"), f4aFrame.widgetIsbn()),
  new Element(new FieldReference(book, "publisher"), f4aFrame.widgetPublisher())
};

frame0.display(elementList1);
...

Details:

When a dynamic title is used, the conversion process will emit it right after the frame.openScope() call. This ensures that, each time the frame is drawn on the screen, the title will be computed from the current value of the referenced field.

Example 2:

def var idx as int.
...
display book.isbn book.publisher with title string(book.comment[idx]).
...

Converted code:

...
frame0.openScope();
frame0.setDynamicTitle(new FieldReference(book, "comment", minus(idx, 1)));
...
FrameElement[] elementList1 = new FrameElement[]
{
  new Element(new FieldReference(book, "isbn"), f4aFrame.widgetIsbn()),
  new Element(new FieldReference(book, "publisher"), f4aFrame.widgetPublisher())
};

frame0.display(elementList1);
...

Details:

For extent fields, the element index is added to the FieldReference@; when a variable is used, conversion process automatically converts the index parameter from a 1-based index to a 0-based index.

Example 3:

...
display book.isbn book.publisher with setup.down down.
...

Converted code:

...
frame0.openScope();
frame0.setDown((Accessor) new FieldReference(setup, "down"));
...
FrameElement[] elementList1 = new FrameElement[]
{
  new Element(new FieldReference(book, "isbn"), f4aFrame.widgetIsbn()),
  new Element(new FieldReference(book, "publisher"), f4aFrame.widgetPublisher())
};

frame0.display(elementList1);
...

Details:

Assuming the setup object references a buffer which holds frame related setup values and setup.down contains the frame-down value, the example above will dynamically set the frame down to the current value from the setup.down field, each time the frame is displayed. If extent fields are used, the converted code is similar to the frame title case - the FieldReference constructor is added a third parameter, the 0-based element index.

Example 4:

...
display book.isbn book.publisher with col setup.column.
...

Converted code:

...
frame0.openScope();
frame0.setColumn((Accessor) new FieldReference(setup, "column"));
...
FrameElement[] elementList1 = new FrameElement[]
{
  new Element(new FieldReference(book, "isbn"), f4aFrame.widgetIsbn()),
  new Element(new FieldReference(book, "publisher"), f4aFrame.widgetPublisher())
};

frame0.display(elementList1);
...

Details:

Assuming the setup object references a buffer which holds frame related setup values and setup.column contains the position of the frame's left column, the example above will dynamically set the frame's left column to the current value from the setup.column field, each time the frame is displayed. If extent fields are used, the converted code is similar to the frame title case - the FieldReference constructor is added a third parameter, the 0-based element index.

Example 5:

...
display book.isbn book.publisher with row setup.row.
...

Converted code:

...
frame0.openScope();
frame0.setRow((Accessor) new FieldReference(setup, "row"));
...
FrameElement[] elementList1 = new FrameElement[]
{
  new Element(new FieldReference(book, "isbn"), f4aFrame.widgetIsbn()),
  new Element(new FieldReference(book, "publisher"), f4aFrame.widgetPublisher())
};

frame0.display(elementList1);
...

Details:

Assuming the setup object references a buffer which holds frame related setup values and setup.row contains the position of the frame's top row, the example above will dynamically set the frame's top row to the current value from the setup.row field, each time the frame is displayed. If extent fields are used, the converted code is similar to the frame title case - the FieldReference constructor is added a third parameter, the 0-based element index.

MESSAGE statement

The database fields can appear in the MESSAGE statement either in the message content or as the updated variable for the MESSAGE statement's UPDATE or SET clause. Regardless of where it appears, when the field is used with the MESSAGE statement, the access to the field will be done using a FieldReference object.

Example 1:

...
message “Book title:” book.book-title.
...

Converted code:

...
message(new Object[]
{
  "Book title:",
  (character) new FieldReference(book, "bookTitle").getValue()
});
...

Details:

Even with the MESSAGE statement, the buffer field is accessed using a FieldReference object, and not directly. The getValue() call is used to invoke the field's getter and retrieve its value, so that it can be displayed by the MESSAGE statement. Also, the getValue() call ensures that the ErrorManager's warning mode is enabled, by bracketing the field getter invocation with ErrorManager.warningModeEnable/warningModeDisable calls.

Example 2:

...
message “Book title” update book.book-title.
...

Converted code:

...
message("Book title:", false, new FieldReference(book, "bookTitle"), "x(35)");
...

Details:

When the field update is performed using the MESSAGE statement, the field access is again done using a FieldReference object.

Example 3:

...
message “Author ID:” book.author-id[1].
...

Converted code:

...
message(new Object[]
{
    "Author ID:",
    (integer) new FieldReference(book, "authorId", 0).getValue()
});
...

Details:

In this case, the element index is converted from 1-based to a 0-based index. All the other behavior is the same as when a non-extent field is used in the message content of the MESSAGE statement.

Example 4:

def var idx as int.
...
idx = 1.
message “Author ID:” book.author-id[idx].
...

Converted code:

...
idx.assign(1);
message(new Object[]
{
    "Author ID:",
    (integer) new FieldReference(book, "authorId", minus((idx), 1)).getValue()
});
...

Details:

In this case too, the first parameter for the FieldReference constructor is the element index. Note how the converted code doesn't emit directly the reference to the local variable idx, but instead it first subtracts 1, so that the passed index will be 0-based, and not 1-based.

PUT SCREEN statement

When a database field is used in a PUT SCREEN statement, the conversion process will always convert it to a simple getter, which will be used to read the current field value.

Example 1:

...
put screen book.book-title.
...

Converted code:

...
putScreen(book.getBookTitle());
...

Details:

A simple getter for the referenced field is used to access the field's current value.

Example 2:

...
put screen "Title: " + book.book-title.
...

Converted code:

...
putScreen(concat("Title: ", book.getBookTitle()));
...

Details:

The same DMO field getter is used even when the field is used in more complex expressions.

Example 3:

...
put screen string(book.author-id[idx]).
...

Converted code:

...
putScreen(valueOf(book.getAuthorId(minus((idx), 1))));
...

Details:

The same DMO field getter is used even when the field is used in more complex expressions.

The stream related statements which can reference database fields are the IMPORT, EXPORT and the PUT statement. In most cases, the simple field reference will be converted to a FieldReference construct, which will be used to invoke the field's getter; when the field is used in a complex expression, the field's getter will be used directly. Following are examples which describe these two cases, by each statement. Note that the FieldReference construct is used in the same way as the other UI statements, previously presented.

4GL Code Converted Code Details
...
import book.book-title.
...
...
UnnamedStreams.safeInput()
.readField(new FieldReference(book, "bookTitle"));

UnnamedStreams.safeInput().resetCurrentLine();
...
The IMPORT statement reads the book title from the currently opened unnamed stream. Here the FieldReference instance is used as a mean to invoke the property setter and update it with the correct value.
...
import stream rpt
book.book-title.
...
...
rptStream.readField(
new FieldReference(book, "bookTitle"));

rptStream.resetCurrentLine();
...
The IMPORT statement reads the book title from the named stream rpt.
...
export book.book-title.
...
...
UnnamedStreams.safeOutput().export(
new FieldEntry[]
{
new ExportField(new FieldReference(book,
"bookTitle"))
});
...
The EXPORT statement writes the book title to the currently opened unnamed stream. The FieldReference instance is used to defer the getter invocation only when the field gets its turn to be exported to the stream.
...
export stream rpt
book.book-title.
...
...
rptStream.export(new FieldEntry[]
{
new ExportField(new FieldReference(book,
"bookTitle"))
});
...
The EXPORT statement writes the book title to the named stream rpt.
...
export stream rpt
"Title: " +
book.book-title.
...
...
rptStream.export(new FieldEntry[]
{
new ExportField(new ExportExpr0())
});
...
private class ExportExpr0
extends GenericExpression
{
public BaseDataType resolve()
{
return concat("Title: ",
book.getBookTitle());
}
}
...
The EXPORT statement writes the book title to the named stream rpt. As the field is used in a complex expression, a simple getter will be used to access the field's value, instead of the FieldReference construct. The complex expression ExportExpr0 will be evaluated only when the field is exported to the stream.
...
put book.book-title.
...
...
UnnamedStreams.safeOutput().put(new FieldEntry[]
{
new PutField(new FieldReference(book,
"bookTitle"),
"x(35)")
});
...
The PUT statement writes the book title to the currently opened unnamed stream. Same as for export, the FieldReference instance is used to defer getter invocation and invoke it only when the field gets its turn to be written to the stream.
...
put stream rpt
book.book-title.
...
...
rptStream.put(new FieldEntry[]
{
new PutField(new FieldReference(book,
"bookTitle"),
"x(35)")
});
...
The PUT statement writes the book title to the named stream rpt.
...
put stream rpt
"Title: " +
book.book-title.
...
...
rptStream.put(new FieldEntry[]
{
new PutField(new PutExpr0())
});
...
private class PutExpr0
extends GenericExpression
{
public BaseDataType resolve()
{
return concat("Title: ",
book.getBookTitle());
}
}
...
The PUT statement writes the book title to the named stream rpt. As the field is used in a complex expression, a simple getter will be used to access the field's value, instead of the FieldReference construct.
...
import stream rpt
book.author-id[1].
...
...
rptStream.readField(new FieldReference(book,
"authorId",
0));
rptStream.resetCurrentLine();
...
The IMPORT statement reads the first book author from the currently opened unnamed stream. Note that the element index for the extent field is converted from a 1-based to a 0-based index.
...
export stream rpt
book.author-id[1].
...
...
rptStream.export(new FieldEntry[]
{
new ExportField(new FieldReference(book,
"authorId",
0))
});
...
The EXPORT statement writes the first book author to the currently opened unnamed stream. Note that the element index for the extent field is converted from a 1-based to a 0-based index.
...
put stream rpt
book.author-id[1].
...
...
rptStream.put(new FieldEntry[]
{
new PutField(new FieldReference(book,
"authorId",
0),
"9999999")
});
...
The PUT statement writes the first book author to the currently opened unnamed stream. Note that the element index for the extent field is converted from a 1-based to a 0-based index.

Other Uses of the FieldReference Class

This section presents cases when the getter invocation needs to be deferred, as it needs to be invoked at some arbitrary point in time after it is referenced in application code, or it needs to access the field's current value at the moment when the expression is evaluated, and not the value when the expression is created. In all these cases, a FieldReference will be emitted.

Fields in Logical Expressions

When logical fields are the right operand of an AND or OR operator, reading the database field must be deferred, so that the field access will be performed only after all left operands were evaluated. This is needed because 4GL stops the evaluation of a logical expression when any of the left operand is false (when the AND operator is used) and when any of the left operand is true (when the OR operator is used). Thus, if the buffer doesn't reference any record, an error will be shown to the client only when the code actually tries to access the field's value (i.e. when the logical expression where the database field is used is evaluated).

Example 1:

def var sell as logical.
...
if sell and book.in-print
   then message "book is in print".
...

Converted code:

...
if (_and(sell, new LogicalExpression(new FieldReference(book, "inPrint"))))
{
    message("book is in print");
}
...

Details:

In this example, a message is displayed only if the sell flag is true and the book is in print. As the book.in-print is used on the right side of the AND operator, the field access is deferred and the field is accessed only if the sell flag is true.

Example 2:

...
if book.price > 100 and book.in-print
   then message "book is in print".
...

Converted code:

...
if (_and(isGreaterThan(book.getPrice(), 100),
         new LogicalExpression(new FieldReference(book, "inPrint"))))
{
   message("book is in print");
}
...

Details:

In this example, a message is displayed only if the book costs more than $100 and is in print. As the book.in-print is used on the right side of the AND operator, the field access is deferred regardless if it is used on the left side of the AND operator. So, the field is accessed only if the book.price > 100 expression is evaluated to true.

Fields in a WHILE Clause

If a block has a WHILE clause and a logical database field is used in the clause, the access to the field will be done using a FieldReference construct. The reasons are similar to the case when a field is on the right side of an AND or OR operator - each time the WHILE clause gets evaluated, it needs the field value in the currently loaded record.

Example 1:

...
repeat while book.in-print:
   ...
end.
...

Converted code:

...
repeatWhile("loopLabel0", new LogicalExpression(new FieldReference(book, "inPrint")), new Block()
{
    public void body()
    {
     ...
    }
});
...

Details:

When used in a WHILE clause, access to the logical fields will be done using the FieldReference construct.

Example 2:

...
repeat while book.in-print and sell:
   ...
end.
...

Converted code:

...
LogicalExpression whileClause0 = new LogicalExpression()
{
    public logical execute()
    {
      return and(book.isInPrint(), sell);
    }
};

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

Details:

In this case, the access to the book.in-print field is done using a simple getter, because the rule “use a FieldReference only if the field is on the right side of an AND or OR operator” applies. This is because the LogicalExpression construct already defers the invocation of the field's getter to the time when the WHILE clause is evaluated, and the field's getter retrieves the value in the currently loaded record each time the WHILE clause is evaluated.

Example 3:

...
repeat while sell and book.in-print:
   ...
end.
...

Converted code:

...
LogicalExpression whileClause1 = new LogicalExpression()
{
    public logical execute()
    {
      return and(sell, new LogicalExpression(new FieldReference(book, "inPrint")));
    }
};

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

Details:

With the operands switched, the book.in-print is on the right side of the AND operator, so a FieldReference is needed to defer the getter invocation and invoke it only when the sell variable is true.

Fields in a TO/BY Clause

Similar to WHILE clause, when the TO clause uses a field as the limit and a transaction is active, a FieldReference construct is needed to access the field's value for the currently loaded record, each time the TO limit condition is evaluated. This is because the field's value can change, while the transaction is active.

Example 1:

...
repeat i = 1 to book.sold-qty transaction:
   ...
end.
...

Converted code:

...
ToClause toClause0 = new ToClause(idx, 1, new FieldReference(book, "soldQty"));

repeatTo(TransactionType.FULL, "loopLabel3", toClause0, new Block()
{
    public void init()
    {
      frame0.openScope();
    }

    public void body()
    {
     ...
    }
});
...

Details:

After each iteration of the block, the TO clause will need to be evaluated. The FieldReference construct ensures the newest value for the field is always used.

Example 2:

...
repeat transaction:
...
   repeat i = 1 to book.sold-qty:
      ...
   end.
...
end.
...

Converted code:

...
repeat(TransactionType.FULL, "loopLabel4", new Block()
{
    public void body()
    {
      ToClause toClause1 = new ToClause(i, 1, new FieldReference(book, "soldQty"));

      repeatTo("loopLabel5", toClause1, new Block()
      {
      public void body()
      {
       ...
      }
      });
    }
});
...

Details:

This example demonstrates that it doesn't matter which block starts the transaction - if the TO clause is used while a transaction is active, a FieldReference construct must be used.

Fields in a Sort BY Clause

When a field is used in a sort clause, in some cases it is needed to sort the results on client side instead of relying on the database to sort the records. This applies to cases when the the database field is used with a BREAK BY clause or the sort uses a complex expression, which can't be evaluated on database level. In these cases, a FieldReference construct is emitted when a database field is used in a BY clause, but its goal is different - the construct is used as a definition of the field, which contains the buffer to which the record belongs and the DMO property name. When FWD runtime sorts the records on client-side, it will temporary place the record in the buffer and use the FieldReference instance to access the field's value. Note that for extent fields, the FieldReference is used as for the other cases previously presented - the element index is emitted as a parameter to the constructor.

Example 1:

...
for each book
   by book.publisher
   by idx:
   ...
end.
...

Converted code:

...
forEach("loopLabel3", new Block()
{
    PresortQuery query3 = null;

    FieldReference byExpr1 = new FieldReference(book, "publisher");

    public void init()
    {
      query3 = new PresortQuery(book, (String) null, null, "book.publisher asc");
      query3.setNonScrolling();
      query3.addSortCriterion(byExpr1);
    }

    public void body()
    {
      query3.next();
      ...
    }
});
...

Details:

Here, the FOR EACH block sorts the result by the book's publisher and by another variable, idx. In this case, although that variable is treated as a “constant” by the conversion engine and has no actual effect on the sort, the sort will not be done by database; instead, the results will be sorted on client side (by FWD runtime) and the sorted field will be registered with the query using a FieldReference instance (byExpr1); this instance will be passed to the query via the addSortCriterion call.

Example 2:

...
for each book
   by book.publisher
   by substring(book.isbn, pos, len):
   ...
end.
...

Converted code:

...
CharacterExpression byExpr2 = new CharacterExpression()
{
  public character execute()
  {
      return substring(book.getIsbn(), pos, len);
  }
};
...
forEach("loopLabel4", new Block()
{
    PresortQuery query4 = null;

    FieldReference byExpr1 = new FieldReference(book, "publisher");

    public void init()
    {
      query4 = new PresortQuery(book, (String) null, null, "book.publisher asc");
      query4.setNonScrolling();
      query4.addSortCriterion(byExpr1);
      query4.addSortCriterion(byExpr2);
    }

    public void body()
    {
      query4.next();
      ...
    }
});
...

Details:

This query sorts the results first by the book's publisher and then using a complex expression, which is some portion of the book's ISBN code. Considering that the complex expression contains variables (pos and len) not available to the database, the sort needs to be done on client side. For the book.publisher field, a FieldReference construct is emitted, as in the previous case. But, for the book.isbn field, a simple getter is enough and there is no need for a FieldReference, as the complex expression (byExpr2) will be evaluated for each retrieved record (after it is temporary placed in the buffer); the getter call will use that temporary record to retrieve the field's value.

Example 3:

...
for each book
   break by book.in-print:
   ...
end.
...

Converted code:

...
forEach("loopLabel2", new Block()
{
    PresortQuery query2 = null;

    FieldReference byExpr1 = new FieldReference(book, "inPrint");

    public void init()
    {
      query2 = new PresortQuery(book, (String) null, null, "book.bookId asc");
      query2.enableBreakGroups();
      query2.setNonScrolling();
      query2.addSortCriterion(byExpr1);
    }

    public void body()
    {
      query2.next();
      ...
    }
});
...

Details:

Using a BREAK BY means using the 4GL's client-side sorting concept. This results in using a FieldReference instance to access the field, although in some cases the BREAK BY clauses may match the index used for the query. Although the FWD runtime will treat these cases differently (i.e. when BREAK BY sorting equals the index and when it does not), the conversion doesn't not distinguish between them and will always create FieldReference instance for the fields which appear in the BREAK BY clause.

Fields Used by Accumulators

When accumulating data for a database field, the accumulator needs to access the field value for the current record, each time the accumulator is invoked. Thus, when instantiating the accumulator, the converted code needs a construct which defers the field getter invocation until the accumulation is performed - this is done using a FieldReference construct, similar to the previous cases. If the accumulator uses a complex expression where database fields are involved, in this case the simple getters will be used to access the field value, as the complex expression will be evaluated (and the field getter will be invoked) each time the accumulation is performed.

Example 1:

...
for each book:
   accum book.sold-qty (total).
end.
...

Converted code:

...
FieldReference fieldRef0 = new FieldReference(book, "soldQty");
TotalAccumulator accumTotal0 = new TotalAccumulator(fieldRef0);
...
forEach("loopLabel11", new Block()
{
  AdaptiveQuery query5 = null;

  public void init()
  {
    query5 = new AdaptiveQuery(book, (String) null, null, "book.bookId asc");
    query5.addAccumulator(accumTotal0);
    accumTotal0.reset();
  }

  public void body()
  {
    query5.next();
    accumTotal0.accumulate();
  }
});
...

Details:

Here, the accumulator computes the total quantity of sold books. The accumulator and the field reference are defined as instance variables for the class which stands for the external procedure. The accumulated expression needs to be evaluated on each accumulation, so the FieldReference construct is used to invoke the field's getter and retrieve the value in the current record.

Note that in some cases, the accumulator and the field reference are promoted to an instance field for the class which stands for the external procedure, and in other cases their scope is limited to the block which uses them. See the Accumulators chapter of this book for more details.

Example 2:

...
for each book:
   accum book.sold-qty * book.price (total).
end.
...

Converted code:

...
forEach("loopLabel10", new Block()
{
  AdaptiveQuery query4 = null;

  DecimalExpression accumExpr0 = new DecimalExpression()
  {
    public decimal execute()
    {
    return multiply(book.getSoldQty(), book.getPrice());
    }
  };

  final TotalAccumulator accumTotal1 = new TotalAccumulator(accumExpr0);

  public void init()
  {
    query4 = new AdaptiveQuery(book, (String) null, null, "book.bookId asc");
    query4.addAccumulator(accumTotal1);
    accumTotal1.reset();
  }

  public void body()
  {
    query4.next();
    accumTotal1.accumulate();
  }
});
...

Details:

This example computes the total value of the sold books. As the accumulator uses a complex expression, the field access will be done using a simple getter. The complex expression will be evaluated each time the accumulation performs and it can safely invoke the getter to determine the field value in the current record.

Fields Used by WHERE Clauses

Similar to complex BY clauses, when the WHERE clause contains complex expressions which can't be evaluated by the database, the records will be filtered on client-side, by the FWD runtime. If the complex expression contains sub-queries (i.e. a CAN-FIND statement) which uses in its WHERE clause some field from a buffer in the parent query, but the sub-query can't be performed on the database (i.e. as it uses a temporary table and the query is for a permanent table), then the field will need to be passed as a parameter to the sub-query using a FieldReference construct and not directly, using the getter. This is needed to defer the field access and get the current field value each time the client-side WHERE clause is evaluated.

Example 1:

...
def temp-table tt-book like book.
...
for each book
   where can-find(tt-book where tt-book.isbn = book.isbn):
   ...
end.
...

Converted code:

...
WhereExpression whereExpr0 = new WhereExpression()
{
  public logical evaluate(final BaseDataType[] args)
  {
      return new FindQuery(ttBook, "upper(ttBook.isbn) = ?", null, (String) null, new Object[]
      {
         new FieldReference(book, "isbn", true)
      }, LockType.NONE).hasOne();
  }
};
...
forEach("loopLabel0", new Block()
{
    AdaptiveQuery query0 = null;

    public void init()
    {
      RecordBuffer.openScope(book);
      RecordBuffer.prepare(ttBook);
      query0 = new AdaptiveQuery(book, (String) null, whereExpr0, "book.bookId asc");
    }

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

Details:

Having a CAN-FIND sub-query for a temp-table, means that any reference to a field which belongs to a buffer in the parent query must be converted to a FieldReference construct. In this example, we iterate all books and must filter only the ones for which there is a record in the tt-book temporary table with the same ISBN. The record filtering will be done on client-side using the whereExpr0, which will determine if a record matches the WHERE clause or not. When the WHERE clause for the nested CA@N-FIND@ is evaluated, the FieldReference instance will be used to retrieve the book.isbn value for the current record.

Database Fields as Parameters

When the field is passed as a parameter to a procedure or function, the field reference will convert differently, depending on the type of the parameter to which the field is passed: is it an input, output or input-output parameter; the converted code will look the same, either the field is passed to a function, procedure or to an external procedure parameter. In this section, when a “procedure parameter” reference is encountered, it means that it refers to any of the three cases - function, procedure or external procedure parameter.

If the field is passed as an input parameter, the field's getter will be used to retrieve the current field value and pass it to the procedure parameter. For output and input-output cases, access to the field will be wrapped using one of the p2j.persist.*Field classes, depending on the field's type (i.e. p2j.persist.CharacterField for a character field, p2j.persist.DecimalField for a decimal field, etc). Any instance of these classes can be used as a normal 4GL variable, as it has as base class the FWD class associated with the field's 4GL type.

The p2j.persist.CharacterField and the other associated classes are needed to be able to pass output and input-output parameters to functions, procedures and external procedures. The following table presents the data wrapper classes which can associate with a DMO property. An instance of these classes is created by business logic to manage the hidden assignment of an input-output or an output parameter to the database field with which it is associated, upon return from a procedure or function. The constructors presented in the table are only for the p2j.persist.net.CharacterField wrapper class; all other wrapper classes provide constructors with the same parameters as the CharacterField class.

Class/Constructor Details
p2j.persist.net.CharacterField
extends character
This class is a wrapper for a character database field.
p2j.persist.net.DateField
extends date
This class is a wrapper for a date database field.
p2j.persist.net.DecimalField
extends decimal
This class is a wrapper for a decimal database field.
p2j.persist.net.HandleField
extends handle
This class is a wrapper for a handle database field.
p2j.persist.net.IntegerField
extends integer
This class is a wrapper for an integer database field.
p2j.persist.net.LogicalField
extends logical
This class is a wrapper for a logical database field.
p2j.persist.net.RawField
extends raw
This class is a wrapper for a raw database field.
public CharacterField(DataModelObject dmo,
String property)
The dmo parameter is the record buffer for the record being accessed. The name is the name of the DMO field.
public CharacterField(DataModelObject dmo,
String property,
int index)
The dmo parameter is the record buffer for the record being accessed. The name is the name of the DMO field.
The index is the element index to access, when property represents an extent field.
public CharacterField(DataModelObject dmo,
String property,
NumberType index)
The dmo parameter is the record buffer for the record being accessed. The name is the name of the DMO field.
The index is the element index to access, when property represents an extent field.
public CharacterField(DataModelObject dmo,
String property
boolean initialize)
The dmo parameter is the record buffer for the record being accessed. The name is the name of the DMO field.
For input-output parameters, the initialize flag will be set to true. This causes the parameter to be initialized with the DMO's current value for the target field, when the called function, procedure or program initializes.
public CharacterField(DataModelObject dmo,
String property,
int index,
boolean initialize)
The dmo parameter is the record buffer for the record being accessed. The name is the name of the DMO field.
The index is the element index to access, when property represents an extent field.
For input-output parameters, the initialize flag will be set to true. This causes the parameter to be initialized with the DMO's current value for the target field, when the called function, procedure or program initializes.
public CharacterField(DataModelObject dmo,
String property,
NumberType index,
boolean initialize)
The dmo parameter is the record buffer for the record being accessed. The name is the name of the DMO field.
The index is the element index to access, when property represents an extent field.
For input-output parameters, the initialize flag will be set to true. This causes the parameter to be initialized with the DMO's current value for the target field, when the called function, procedure or program initializes.

Fields as INPUT Parameters

Passing a field as an input parameter is as simple as passing the field's value. The converted code will do this by emitting the property getter, whenever the field is used as an input parameter.

Example 1:

...
procedure foo:
  define input parameter txt as char.
  ...
end.
...
run foo(input book.book-title).
...

Converted code:

...
foo(book.getBookTitle());
...

Details:

The foo procedure has an input parameter of type char@acter@. When it is invoked, the book.book-title field is passed as a parameter. As only the field's value is needed (to set the initial value for the txt parameter), the field's getter is used to retrieve the book title.

Example 2:

...
function foo returns char (input txt as char):
  ...
end.
...
message foo(input book.book-title).
...

Converted code:

...
message(foo(book.getBookTitle()));
...

Details:

The foo function has an input parameter of type char@acter@. When it is invoked, the book.book-title field is passed as a parameter. As only the field's value is needed (to set the initial value for the txt parameter), the field's getter is used to retrieve the book title.

Example 3:

...
run bar.p(input book.book-title).
...

Converted code:

...
Bar bar = new Bar();
bar.execute(book.getBookTitle());
...

Details:

The bar.p is an external procedure which has an input parameter of type char@acter@. When it is invoked, the book.book-title field is passed as a parameter. As only the field's value is needed (to set the initial value for the procedure's parameter), the field's getter is used to retrieve the book title.

Example 4:

...
procedure foo:
  define input parameter auth-id as int.
  ...
end.
...
run foo(input book.author-id[1]).
...

Converted code:

...
foo(book.getAuthorId(0));
...

Details:

In case of an extent fields, the converted code uses the special getter associated with the extent field, in the same manner as a getter for a non-extent field.

Fields as OUTPUT Parameters

Passing a field to a procedure as an output parameter means that any change made to the parameter during the procedure call will also reflect in the database field, upon the normal exit of the procedure. The solution is to wrap the access to the field using a p2j.net.persist.type.*Field instance, depending on the field's type; this registers the field with the TransactionManager, so that the field will be updated with the correct value, if the procedure performs a clean exit.

Example 1:

...
procedure foo:
  define output parameter txt as char.
  ...
end.
...
run foo(output book.book-title).
...

Converted code:

...
foo(new CharacterField(book, "bookTitle"));
...

Details:

The foo procedure has an output parameter of type char@acter@. Passing the book.book-title field as an output parameter means wrapping the access to the field using the CharacterField class. The usage of this class is similar to the FieldReference class, as only the book buffer and the field's converted name (i.e. the DMO property name) are required. Note that the p2j.net.persist.type.*Field classes are emitted depending on the field's type.

Example 2:

...
function foo returns char (output txt as char):
  ...
end.
...
message foo(output book.book-title).
...

Converted code:

...
message(foo(new CharacterField(book, "bookTitle")));
...

Details:

There is no difference when a field is passed as an output parameter to a function - the same CharacterField construct (or equivalent, depending in the field's type) is used.

Example 3:

...
run bar.p(output book.book-title).
...

Converted code:

...
Bar bar = new Bar();
bar.execute(new CharacterField(book, "bookTitle"));
...

Details:

The bar.p is an external procedure which has an output parameter of type char@acter@. The converted code is similar with the previous cases - the wrapper class is used to gain access to the field's setter and update it upon return from the external procedure.

Example 4:

...
procedure foo:
  define output parameter auth-id as int.
  ...
end.
...
run foo(output book.author-id[1]).
...

Converted code:

...
foo(new CharacterField(book, "bookTitle", 0));
...

Details:

In case of an extent fields, the element index will be passed to the constructor, similar to how the FieldReference class is used with an extent field.

Fields as INPUT-OUTPUT Parameters

Using an input-output parameter means that the parameter will act as both an input and an output parameter, at the same time: this requires for the parameter to be initialized with the current field value and also to update the field, upon a successful return from the procedure. In this case, a simple getter is not enough - instead, the converted code will emit the constructor for the wrapper class with the initialize parameter set to true; this will allow the wrapper class to initialize itself with the current field value.

Example 1:

...
procedure foo:
  define input-output parameter txt as char.
  ...
end.
...
run foo(input-output book.book-title).
...

Converted code:

...
foo(new CharacterField(book, "bookTitle", true));
...

Details:

The foo procedure has an input-p@utput@ parameter of type char@acter@. Passing the book.book-title field as a parameter means wrapping the access to the field using the CharacterField class. As this class is also a FWD character, it will initialize itself with the current field value and the procedure's parameter will contain the current field value. Beside the constructor's initialize parameter being set to true, the converted code is the same as when an output parameter is used.

Example 2:

...
function foo returns char (input-output txt as char):
  ...
end.
...
message foo(input-output book.book-title).
...

Converted code:

...
message(foo(new CharacterField(book, "bookTitle", true)));
...

Details:

There is no difference when a field is passed as an input-o@utput@ parameter to a function - the same CharacterField construct (or equivalent, depending in the field's type) is used.

Example 3:

...
run bar.p(input-output book.book-title).
...

Converted code:

...
Bar bar = new Bar();
bar.execute(new CharacterField(book, "bookTitle", true));
...

Details:

The bar.p is an external procedure which has an input-o@utput@ parameter of type char@acter,@ and the converted code is similar with the previous cases - the wrapper class is instantiated in the same way.

Example 4:

...
procedure foo:
  define input-output parameter auth-id as int.
  ...
end.
...
run foo(input-output book.author-id[1]).
...

Converted code:

...
foo(new CharacterField(book, "bookTitle", 0, true));
...

Details:

For the extent fields case, the element index will be passed to the constructor similar to how the FieldReference class is used with an extent field. Note that the constructor's initialize parameter (last in the list) is set to true in this case, too.


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