Once you create a dynamic query or assign a handle to a static one, you can retrieve (and, in some cases, set) information about the query using its methods and attributes. This section provides a summary of those methods and attributes. Unless otherwise noted, they all apply to handles for both static and dynamic queries.

DYNAMIC attribute

The DYNAMIC attribute returns TRUE if the query is dynamic and FALSE if it is static.

SET-BUFFERS() method

The SET-BUFFERS( ) method takes a buffer handle or a comma-separated list of buffer handles and sets the query’s buffer list to those buffers. It returns TRUE if the operation succeeded and FALSE otherwise.

You can pass a buffer reference in one of several ways:
  • Using the BUFFER buffer-name:HANDLE syntax, which provides a handle for a static buffer on a known table.
  • Using a handle variable or field that you associate with a static buffer in a separate statement, such as hBuffer = BUFFER buffer-name:HANDLE.
  • Using the handle for a dynamic buffer, where you name the table only at run time.
For example, this sequence of statements creates a query and sets its buffer list to the Order and Customer buffers:
VAR HANDLE hQuery.

CREATE QUERY hQuery.
hQuery:SET-BUFFERS(BUFFER Order:HANDLE, BUFFER Customer:HANDLE).
This is the equivalent of defining a static query for those buffers, as you could do with this statement:
DEFINE QUERY OrderCust FOR Order, Customer.
You could then assign a handle to the static query in the same way, for example:
VAR HANDLE hQuery.
DEFINE QUERY OrderCust FOR Order, Customer.

hQuery = QUERY OrderCust:HANDLE.

This raises the basic question of when to use static and when to use dynamic queries. Typically, unless your procedure is of such general use that you do not know the tables or buffers it will use until run time, you can define a static query and then use its handle to modify it as needed at run time. This is the case if, for example, you need to modify the WHERE clause or sort order of the query in a variety of ways at run time. If you start with a static query, you can use any combination of the static statements you’re familiar with to manipulate it, such as GET FIRST, OPEN QUERY, and so forth, or their dynamic equivalents, which are introduced in the following section. If you start with a dynamic query, you can use only dynamic methods to manipulate it.

The one aspect of a static query you cannot modify at run time is its buffer list, so the SET-BUFFERS() method applies only to dynamic queries.

ADD-BUFFER() method

The ADD-BUFFER( ) method takes a single buffer handle as an argument and adds it to the list of buffers for the query. In this way, you can have a programming loop that adds a sequence of buffers to a query one at a time, when that is appropriate and when your query needs to join two or more buffers. It returns TRUE if the operation succeeded and FALSE otherwise.

For example, you can use the ADD-BUFFER() method to add a third buffer to the query that already has the Order and Customer buffers assigned to it:
VAR HANDLE hQuery.

CREATE QUERY hQuery.
hQuery:SET-BUFFERS(BUFFER Order:HANDLE, BUFFER Customer:HANDLE).
hQuery:ADD-BUFFER(BUFFER SalesRep:HANDLE).

Like the SET-BUFFERS() method, ADD-BUFFER() is supported only for dynamic queries for the same reason that you cannot modify the buffer list of a static query.

NUM-BUFFERS attribute

The NUM-BUFFERS attribute returns the number of buffers for the query:
VAR HANDLE hQuery.

CREATE QUERY hQuery.
hQuery:SET-BUFFERS(BUFFER Order:HANDLE, BUFFER Customer:HANDLE).
hQuery:ADD-BUFFER(BUFFER SalesRep:HANDLE).
MESSAGE "This dynamic query has "
  hQuery:NUM-BUFFERS "buffers." VIEW-AS ALERT-BOX.
This is the result of running the code:

GET-BUFFER-HANDLE() method

Given the number of buffers in the query, you can walk through them and retrieve each handle in turn, using the GET-BUFFER-HANDLE( ) method, which takes the sequential buffer number as a parameter, as in this example:
VAR HANDLE hQuery.
VAR INTEGER iBufNum.
VAR CHARACTER cBufNames.

CREATE QUERY hQuery.
hQuery:SET-BUFFERS(BUFFER Order:HANDLE, BUFFER Customer:HANDLE).
hQuery:ADD-BUFFER(BUFFER SalesRep:HANDLE).
DO iBufNum = 1 TO hQuery:NUM-BUFFERS:
  cBufNames = cBufNames + hQuery:GET-BUFFER-HANDLE(iBufNum):NAME
    + " ".
END.
MESSAGE "This query uses buffers " cBufNames VIEW-AS ALERT-BOX.

This is the result of running the code:

NAME attribute

As you know, only static objects have names, so the NAME attribute only applies to static queries:
VAR HANDLE hQuery.

DEFINE QUERY OrderCust FOR Order, Customer.

hQuery = QUERY OrderCust:HANDLE.
MESSAGE "This is a static query named " hQuery:NAME VIEW-AS ALERT-BOX.
This is the result of running the code:

QUERY-PREPARE() method

Once you define the buffers for a dynamic query, the next step is to provide it with a FOR EACH statement that it should use to retrieve order data when you open the query. The QUERY-PREPARE( ) method takes the FOR EACH statement as its argument. It returns TRUE if the operation succeeded and FALSE otherwise. You can use QUERY-PREPARE() to define the record selection for a dynamic query or to change the selection for a static one. This sample code prepares the dynamic query for the Order, Customer, and SalesRep tables:
VAR HANDLE hQuery.
VAR INTEGER iBufNum.
VAR CHARACTER cBufNames.
VAR LOGICAL lSuccess.

CREATE QUERY hQuery.
hQuery:SET-BUFFERS(BUFFER Order:HANDLE, BUFFER Customer:HANDLE).
hQuery:ADD-BUFFER(BUFFER SalesRep:HANDLE).
lSuccess =
  hQuery:QUERY-PREPARE("FOR EACH Order WHERE OrderStatus = 'Ordered', "
    + "FIRST Customer OF Order, "
    + "FIRST SalesRep OF Order "
    + "BY SalesRep").

IF NOT lSuccess OR ERROR-STATUS:NUM-MESSAGES NE 0 THEN
DO:
  /* Deal with possible errors in the Query Prepare. */
END.
Note: Using the structured error handling model, built-in ABL methods like QUERY-PREPARE() raise ERROR if the block they occur in contains a CATCH block. Thus, you could replace the IF statement with a CATCH block for more readable code. See ABL Error Handling for more information.
There are a few important comments to make about this example:
  1. Remember that when you use a one-to-one join in a FOR EACH statement for a query, you must include the FIRST or EACH keyword in each table phrase, as is shown in the following code example. Since there is just one Customer for each Order, and one SalesRep for each Order, there is no need to iterate through potentially multiple Customers or SalesReps for an Order, so the FIRST keyword suffices.
    In a FOR EACH statement in a block header, the FIRST keyword would be optional, as in this example:
    FOR EACH Order WHERE OrderStatus = "Ordered",
      Customer OF Order, SalesRep OF Order:
      DISPLAY OrderNum Order.CustNum Customer.NAME SalesRep.RepName.
    END.
  2. If you know exactly what the WHERE clause and BY clause are when you write the procedure, you might not need to use dynamic constructs at all. More realistically, you use the QUERY-PREPARE() method when you don’t know the selection and sort criteria until run time.
  3. Since you normally use the QUERY-PREPARE() method in cases where the WHERE clause is truly variable, it is essential that you always assign the result of the method to a logical variable, and then check the value of that variable and the ERROR-STATUS:NUM-MESSAGES value to be sure that the prepare succeeded. If it does not and you continue without intercepting the error, your procedure generates a whole series of error statements as you attempt to open and use the query.

PREPARE-STRING attribute

Once you have prepared a query, you can verify what its current FOR EACH statement is using through the PREPARE-STRING attribute, which returns the effect of the most recent QUERY-PREPARE() method, as in this example:
MESSAGE hQuery:PREPARE-STRING VIEW-AS ALERT-BOX.
This shows the result:

The PREPARE-STRING attribute applies only to dynamic queries. It does not return the FOR EACH statement used in an OPEN QUERY statement for a static query. .

QUERY-OPEN() method

Once you have prepared a query you need to open it, using the QUERY-OPEN( ) method:
hQuery:QUERY-OPEN().

The QUERY-PREPARE() and QUERY-OPEN() methods together accomplish what the OPEN QUERY statement does for a static query. The two methods are separated out to enable you to define the selection and sort criteria for a query separately from opening it. You might want to reopen the same query several times with the same FOR EACH statement, for example, to capture changes to the underlying data the FOR EACH statement retrieves. Having two separate methods gives you this flexibility. It also allows you to verify that the FOR EACH statement in a QUERY-PREPARE() is valid before you try to open the query.

You can use the QUERY-PREPARE() and QUERY-OPEN() methods with static queries as well, even after the query has been opened one or more times with a static OPEN QUERY statement. Here’s an example that extends the static query shown earlier:
VAR HANDLE hQuery.
VAR CHARACTER cSalesRep.
 
DEFINE QUERY OrderCust FOR Order, Customer. /* Static definition */

hQuery = QUERY OrderCust:HANDLE. /* Capture the handle */
OPEN QUERY OrderCust FOR EACH Order WHERE Order.SalesRep = "BBB",
  FIRST Customer OF Order. /* static OPEN */
MESSAGE "Static OPEN can't show PREPARE-STRING: " /* This is UNKNOWN */
  hQuery:PREPARE-STRING VIEW-AS ALERT-BOX.
CLOSE QUERY OrderCust.

/* Code to ask the user for a SalesRep could go here... */
cSalesRep = "DKP".

/* Now use the dynamic methods to re-prepare and re-open the query. */
hQuery:QUERY-PREPARE("FOR EACH Order WHERE SalesRep = '" +
  cSalesRep + "', FIRST Customer OF Order").
hQuery:QUERY-OPEN().
MESSAGE "QUERY-PREPARE sets PREPARE-STRING:" SKIP
hQuery:PREPARE-STRING VIEW-AS ALERT-BOX.
The first alert box confirms that you can’t use the PREPARE-STRING attribute on a static OPEN:
But the next alert box shows that you can use it when you prepare and open even a static query using the dynamic methods:

Using the QUOTER function to assemble a query

Notice the use of the single quotation marks embedded in the strings that are assembled to build up the FOR EACH statement:
hQuery:QUERY-PREPARE("FOR EACH Order WHERE SalesRep = '" +
  cSalesRep + "', FIRST Customer OF Order").

The first literal ends with a single quote mark, which precedes the SalesRep initials. This, in turn, is followed by another literal that begins with a single quote mark to balance the first one. This kind of assemblage of strings with and without quote marks can get very tricky and lead to code that is hard to read and programming errors that are hard to identify. Here, for example, you need to remember not only to put the quote marks into the statement before and after the cSalesRep value, but also to use single quotes so that they don’t interfere with the use of double-quotes around the literals themselves. You could reverse these and use double quotes inside single quotes, but either way you need to be sure to balance them and embed them properly.

ABL provides a helpful function to assist you in assembling these kinds of strings, the QUOTER function. If you insert values of any data type into a string, such as this QUERY-PREPARE() as arguments to the QUOTER function, the ABL Virtual Machine (AVM) assembles the string properly so that you don’t need to worry about where the embedded quote marks go. For instance, here’s an alternative version of the QUERY-PREPARE() method that uses QUOTER:
hQuery:QUERY-PREPARE("FOR EACH Order WHERE SalesRep = " +
  QUOTER(cSalesRep) + ", FIRST Customer OF Order").
This alert box shows that the AVM inserts quote marks around the SalesRep initials for you:

You can use QUOTER to avoid parsing errors for numeric values as well as string values. For example, DECIMAL formats in parts of the world outside the United States typically reverse the meaning of the decimal point or period/full stop (.) and comma (,) characters. If you use QUOTER on all such values, the AVM assembles a query string that compiles properly without causing confusion to the syntax analyzer, which must interpret whether the “.” character is a decimal point, a separator between thousands in a numeric value, or the end of a statement.

QUERY-CLOSE() method

There is a dynamic method, the QUERY-CLOSE( ) method, to close a query as well:
hQuery:QUERY-CLOSE().

QUERY-CLOSE() returns TRUE if the operation succeeded and FALSE otherwise. As with the CLOSE QUERY statement, you don’t need to use QUERY-CLOSE() if you are immediately again preparing and reopening a query, but you should close a query when you are done using it. You can freely mix and match static OPEN and CLOSE statements and QUERY-OPEN() and QUERY-CLOSE() methods for static queries but, as with all the query syntax, you can use the static statements only for static queries.

QUERY-OFF-END attribute

The QUERY-OFF-END attribute corresponds to the QUERY-OFF-END function for static queries. It returns TRUE if the query is no longer positioned to a record in the result set, either because you have proceeded beyond the last row or moved backward beyond the first row.

IS-OPEN attribute

The LOGICAL IS-OPEN attribute returns TRUE is the query is open and FALSE otherwise.