Processing GROUP Fields
Understanding GROUP Fields
The GROUP statement defines a data structure within a record or another group structure. The ENDGROUP statement indicates the end of a group declaration. A group name must be unique among all other field and group names within a parent group. If a group isn’t part of another group, its name must be unique within the record. Here is an example of a record containing several groups:
record info
group customer ,[100]a
name ,a30
group office ,a
bldg ,a20
group address ,a
street ,a40
zip ,d10
endgroup
endgroup
group contact ,a
name ,a40
group address ,a
street ,a40
zip ,d10
endgroup
endgroup
endgroup
endrecord
Overlay Groups
Group fields can also be used to define overlays. By default, CodeGen excludes overlays when processing, and that included the exclusion of overlay groups. However, if the -f o command line option is used to cause overlays to be included when processing, then overlay groups will also be included. When processing in a field loop you can detect whether an unexpanded overlay group is being processed by using the <IF GROUP_OVERLAY> expression.
Implicit and Explicit Groups
While DBL has only one type of group, Repository allows you to define groups in two ways. These are referred to as "explicit groups" and "implicit groups". An explicit group is a group where the group members are defined explicitly within the definition of the group, all within the same structure. An implicit group is a group where the members of the group are defined by the members of a different structure.
Group Member Prefix
When a field is declared as a GROUP it is possible to also provide a "member prefix", which is a value to be pre-pended to the names of all fields in the group. By default, CodeGen will use any prefix specified, but if you prefer not to use the prefix when generating code then that behavior can be suppressed by specifying the -g f command line option.
When processing groups, you can determine whether a group member prefix is present by using the <IF GROUP_MEMBER_PREFIX> expression, and you can access the prefix value with the <FIELD_GROUP_MEMBER_PREFIX> expansion token.
How CodeGen Processes Groups
Under normal circumstances, whenever CodeGen encounters a GROUP field in a structure, it expands the group into individual fields. So, if a structure contains a group that has ten member fields, then the group field is removed from the structure and ten individual fields are added in its place. And if a group contains other "nested" groups, then the same process is applied to those fields also. For example, given the following structure:
structure customer
customer_id, a10
company_name, a40
group address, a
street, a30
city, a20
state, a2
zip, d5
endgroup
phone, d10
endstructure
If you use the <field_name> token to access the field names in a field loop, you would see field names like this:
customer_id
company_name
address.street
address.city
address.state
address.zip
phone
If you use the <field_sqlname> token to access the field names in a field loop, you would see field names like this:
customer_id
company_name
address_street
address_city
address_state
address_zip
phone
Controlling Group Expansion
If you prefer to not have group fields expanded during code generation then you can control that behavior, for both explicit and implicit groups, via two command-line options. The -g e option prevents explicit groups from being expanded, and the -g i option prevents the expansion of implicit groups.
When you use these options the original group field remains in the structure, and the values of some field loop tokens is altered for those fields. When processing a field loop you can detect whether groups are or are not being expanded using expression tokens <IF EXPLICIT_GROUP_NO_EXPAND> and <IF IMPLICIT_GROUP_NO_EXPAND>, and you can detect unexpanded group fields with expressions like <IF GROUP>, <IF EXPLICIT_GROUP>, <IF IMPLICIT_GROUP> and <IF GROUP_OVERLAY>. And when processing an unexpanded implicit group, you can refer to the name of the structure that defines the members of the group via the <FIELD_GROUP_STRUCTURE> token.
Generating Code for Unexpanded Groups
This is a complex subject, and one for which there are likely to be many different scenarios, and different requirements, but here is one example, based on the following structure:
structure CUSTOMER
CUSTOMER_ID ,D8 ; Customer ID
COMPANY ,A40 ; Company name
group BILLING_ADDRESS ,A ; Billing address
STREET ,A30 ; Street address
CITY ,A20 ; City
STATE ,A2 ; State
ZIP ,D5 ; Zip code
endgroup ;
group SHIPPING_ADDRESS ,A ; Shipping address
STREET ,A30 ; Street address
CITY ,A20 ; City
STATE ,A2 ; State
ZIP ,D5 ; Zip code
endgroup ;
group PRIMARY_CONTACT ,A ; Primary contact name
FIRST_NAME ,A15 ; First name
MIDDLE_INITIAL ,A1 ; Middle initial
LAST_NAME ,A15 ; Last name
endgroup ;
group BILLING_CONTACT ,A ; Billing contact
FIRST_NAME ,A15 ; First name
MIDDLE_INITIAL ,A1 ; Middle initial
LAST_NAME ,A15 ; Last name
endgroup ;
group SHIPPING_CONTACT ,A ; shipping contact
FIRST_NAME ,A15 ; First name
MIDDLE_INITIAL ,A1 ; Middle initial
LAST_NAME ,A15 ; Last name
endgroup ;
endstructure
In the repository, the groups BILLING_ADDRESS and SHIPPING_ADDRESS are defined as IMPLICIT groups, their fields being defined by a second structure named ADDRESS. Whereas the groups PRIMARY_CONTACT, BILLING_CONTACT and SHIPPING_CONTACT are all defined as explicit groups, i.e. their member fields are defined LOCALLY within the CUSTOMER structure
If we wanted to generate a Synergy .NET class to represent this structure, we might use a template like this:
namespace <NAMESPACE>
public class <StructureName>
<FIELD_LOOP>
public readwrite property <FieldSqlName>, <FIELD_SNTYPE>
</FIELD_LOOP>
endclass
endnamespace
And we might use a codegen command like this:
codegen -t grouptest -s customer -n MyNamespace
And the output would be something like this:
namespace MyNamespace
public class Customer
public readwrite property CustomerId, int
public readwrite property Company, String
public readwrite property BillingAddressStreet, String
public readwrite property BillingAddressCity, String
public readwrite property BillingAddressState, String
public readwrite property BillingAddressZip, int
public readwrite property ShippingAddressStreet, String
public readwrite property ShippingAddressCity, String
public readwrite property ShippingAddressState, String
public readwrite property ShippingAddressZip, int
public readwrite property PrimaryContactFirstName, String
public readwrite property PrimaryContactMiddleInitial, String
public readwrite property PrimaryContactLastName, String
public readwrite property BillingContactFirstName, String
public readwrite property BillingContactMiddleInitial, String
public readwrite property BillingContactLastName, String
public readwrite property ShippingContactFirstName, String
public readwrite property ShippingContactMiddleInitial, String
public readwrite property ShippingContactLastName, String
endclass
endnamespace
But what if we didn't want to expand the groups into individual properties. What happens if we add the command line options to tell CodeGen not to expand explicit and implicit groups, like this:
codegen -t grouptest -s customer -n MyNamespace -g e i
Well, we wind up with this:
namespace MyNamespace
public class Customer
public readwrite property CustomerId, int
public readwrite property Company, String
public readwrite property BillingAddress, @ADDRESS
public readwrite property ShippingAddress, @ADDRESS
public readwrite property PrimaryContact, @PRIMARY_CONTACT
public readwrite property BillingContact, @BILLING_CONTACT
public readwrite property ShippingContact, @SHIPPING_CONTACT
endclass
endnamespace
As you can see, the group fields were not expanded, and the data type produced for the unexpanded groups by the <FIELD_SNTYPE> token became a class reference. In the case of the two implicit group fields, BILLING_ADDRESS and SHIPPING_ADDRESS, the type refers to a class defined by the name of the other structure that was used to define the group members, and in the case of the three explicit groups, the type refers to a class based on the name of the original group field.
This code may or may not compile, depending on whether the three other classed being referred to actually exist or not. Let's explore how we can leverage the functionality of the special <FIELD_GROUP_EXPAND> token to enhance out template:
namespace <NAMESPACE>
public class <StructureName>
<FIELD_LOOP>
<IF GROUP>
<IF IMPLICIT_GROUP>
public readwrite property <FieldSqlName>, @<FIELD_GROUP_STRUCTURE>
<ELSE>
public class <FieldName>
<FIELD_GROUP_EXPAND>
endclass
public readwrite property <FieldSqlName>, @<FieldName>
</IF IMPLICIT_GROUP>
<ELSE>
public readwrite property <FieldSqlName>, <FIELD_SNTYPE>
</IF GROUP>
</FIELD_LOOP>
endclass
endnamespace
This template is a little hard to explain, so we'll break down the most important parts of the template one at a time:
1.Within the field loop, notice that the first thing we do is use an <IF GROUP> expression to give us a place to do special things for unexpanded groups, and the associated <ELSE> clause provides a place for the code for regular, non-group fields. In fact the code in that <ELSE> clause is the same code that we had in our original template, so for non-group fields we will continue to produce a public read-write property.
2.In the unexpanded group section of the field loop, notice how the next thing we do is use the <IF EXPLICIT_GROUP> expression, and it's associated <ELSE> clause, to allow us to generate different code for implicit groups and explicit groups.
3.For implicit groups we generate a read-write property and type it using the <FIELD_GROUP_STRUCTURE> token. This will result in a reference to the other structure that defines the members for the group.
4.For explicit groups we do a little more; we output the code for a nested class (a class within a class, just like a group is kind of like a record within a record), named after the original explicit group field, and we also emit a public property whose type is defined by that new nested class.
5. This is where it gets complicated. Notice that the body of the nested class contains a single token called <FIELD_GROUP_EXPAND>, which is a very special token that behaves very differently to most other field loop expansion tokens. Essentially what it does is "replay" the entire body of the field loop that contains it, but for the members of the current group field. If the group field only contains fields as members, then the nested class will simply contain public properties for those fields, but if the group contains members that are other groups, then the exact same process will occur, and on, and on, until all fields have been processed.
So, if we process this template, using the exact same CodeGen command that we used last time, the output will look like this:
namespace MyNamespace
public class Customer
public readwrite property CustomerId, int
public readwrite property Company, String
public readwrite property BillingAddress, @ADDRESS
public readwrite property ShippingAddress, @ADDRESS
public class PrimaryContact
public readwrite property FirstName, String
public readwrite property MiddleInitial, String
public readwrite property LastName, String
endclass
public readwrite property PrimaryContact, @PrimaryContact
public class BillingContact
public readwrite property FirstName, String
public readwrite property MiddleInitial, String
public readwrite property LastName, String
endclass
public readwrite property BillingContact, @BillingContact
public class ShippingContact
public readwrite property FirstName, String
public readwrite property MiddleInitial, String
public readwrite property LastName, String
endclass
public readwrite property ShippingContact, @ShippingContact
endclass
endnamespace
As you can see, the regular fields in the structure are exposed as public properties, the implicit groups are exposed as class references to an externally defined class, presumably code-generated separately using a different template, and the explicit groups are implemented as nested classes exposed via public properties.
By the way, if you were to process this token without the -g e i command line options, it would produce the exact same content as the original template, because there would not be any unexpended group fields present.
And I should also mention that if you use the sample templates from this article, the output won't be exactly as shown here; we are still working on how to get appropriate indentation when we iterate through multiple levels of groups with a single expansion token, so for the time being the code may look a little onorganized, but that's nothing that a quick CTRL+K+D in the Visual Studio editor can't fix!
Copyright © 2021 Synergex International, Inc.