RESEARCH ON UNMANAGED IMPLEMENTATION OF TRANSACTIONAL BUFFER FOR RESTFUL APPLICATION PROGRAMMING MODEL
Ilya Prusakou
ABAP Developer & Researcher
Abstract.
Implementing unmanaged scenarios in the ABAP RESTful Application Programming Model (RAP) remains a significant hurdle for developers due to the opinionated nature of the framework and a lack of production-level examples.
The research addresses the challenge by providing comprehensive blueprint for the unmanaged transactional buffer. This article serves as a practical guide for mastering the internal state management of RAP business objects.
KEYWORDS: Unmanaged Implementation, Transactional Buffer, Buffer Tables, Complex Data Source, Buffer Preparation, Buffer Flags, Draft Buffer, Buffer Access, Transactional Phase, Save Phase, Late Numbering.
Examples.
You can find example of buffer implementation on my github: https://github.com/IlyaPrusakou/rap-unmanaged-buffer-example.git
The specific path is src/zpru_transactional_buffer/zpru_cl_example_buffer.clas.abap
You can also review the buffer class code at the bottom of this article. In this post, I will use term "blueprint" referring to the full code of the example implementation.
Additionally, you can visit my another github rep: https://github.com/IlyaPrusakou/abap-rap-example.git
My unmanaged imlementation class on which I have provided my research is under the path: src/zpru_um_po/zpru_um_po_implmtns/zbp_pru_u_purcorderhdr_tp.clas.locals_imp.abap
There, you can look onto the local class LCL_BUFFER.
To check out how to use the buffer for standard operations, you can visit the local handler class LHC_ORDERTP, specifically methods:
- LHC_ORDERTP~CREATE
- LHC_ORDERTP~READ
- LHC_ORDERTP~UPDATE
- LHC_ORDERTP~DELETE
- LHC_ORDERTP~RBA_ITEMS_TP
- LHC_ORDERTP~CBA_ITEMS_TP
For investigation of the child node, you can open the local handler LHC_ITEMTP, specifically methods:
- LHC_ITEMTP~READ
- LHC_ITEMTP~UPDATE
- LHC_ITEMTP~DELETE
- LHC_ITEMTP~RBA_HEADER_TP
It is recomended to have a glance into the local saver class LSC_ZPRU_U_PURCORDERHDR_TP to understand how a trigger calculation works and what role method FINALIZE, CHECK_BEFORE_SAVE and SAVE play.
I also recommend visiting the packages SABAP_DEMOS_RAP and SABAP_DEMOS_RAP_CLOUD to check out various RAP examples, especially BDEF DEMO_RAP_UNMANAGED_DRAFT_ROOT and its associated class.
For the blueprint implementation I have used a RAP business object with an unmanaged implementation type and with four nested levels.
Moreover, I used a late numbering because it requires the maximum implementation effort. I also defined keys using simple character types rather than GUID fields.
Buffer class.
From an implementation perspective a transactinal buffer is ABAP class, usually local class placed in local implementation include of ABAP behavior implementation global class (ABP class). It contains entity buffer tables.
Buffer class can be an ABAP global class as well. However, if you choose this way of implementation you must be aware that global class is considered as external to the ABP class and you will not be able to access internal derived types.
| Accessible types | Internal types |
| keys type table for read import bdef\\entity | keys type table for validation bdef\\entity~validation |
| entitie type table for create bdef\\entity | keys type table for determination bdef\\entity~determination |
| keys type table for action import bdef\\entity~action | event type table for event bdef~event |
| etc. | etc. |
Entity buffer tables.
Entity buffer tables are internal tables defined as static attributes in a buffer class. Their rows contain entity fields from the transactional CDS, flags (changed, deleted) and other components(CID, PID etc.)
In the snippet of code below, you can observe entity buffer tables on rows 42 - 45. Besides the tables themselves, there are types for entity keys and types for the buffer rows as well.
CLASS zpru_cl_example_buffer DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
" types for entity keys
TYPES: BEGIN OF ts_first_node_db_keys,
firstkey TYPE zr_pru_1st_node-firstkey,
END OF ts_first_node_db_keys.
TYPES: BEGIN OF ts_second_node_db_keys,
firstkey TYPE zr_pru_2nd_node-firstkey,
secondkey TYPE zr_pru_2nd_node-secondkey,
END OF ts_second_node_db_keys.
*****
*****
" types for buffer row
TYPES: BEGIN OF ts_first_node,
instance TYPE zr_pru_1st_node,
cid TYPE abp_behv_cid,
pid TYPE abp_behv_pid,
final_key TYPE ts_first_node_db_keys,
changed TYPE abap_bool,
deleted TYPE abap_bool,
END OF ts_first_node.
TYPES: BEGIN OF ts_second_node,
instance TYPE zr_pru_2nd_node,
cid TYPE abp_behv_cid,
pid TYPE abp_behv_pid,
pidparent TYPE abp_behv_pid,
final_key TYPE ts_second_node_db_keys,
changed TYPE abap_bool,
deleted TYPE abap_bool,
END OF ts_second_node.
*****
*****
" entity buffer tables
TYPES tt_first_node TYPE TABLE OF ts_first_node WITH EMPTY KEY.
TYPES tt_second_node TYPE TABLE OF ts_second_node WITH EMPTY KEY.
TYPES tt_third_node TYPE TABLE OF ts_third_node WITH EMPTY KEY.
TYPES tt_fourth_node TYPE TABLE OF ts_fourth_node WITH EMPTY KEY.
CLASS-DATA st_first_node TYPE tt_first_node.
CLASS-DATA st_second_node TYPE tt_second_node.
CLASS-DATA st_third_node TYPE tt_third_node.
CLASS-DATA st_fourth_node TYPE tt_fourth_node.
*****
*****
ENDCLASS.Buffer preparation.
The buffer preparation is the process of loading entities from a complex data source (in my case, it is just a transactional CDS) into entity buffer tables, usually executed via static methods (e.g. PREP_ENTITY_NAME) in the buffer class.
Each standard RAP operations will start with the buffer preparation methods. You may know these operations by names such as preloading, fetching, selecting data into the buffer, or even prepopulating the buffer.
Moreover, I want to draw attention to type definitions for the input parameters of the preparation methods - especially to the flag FULL_KEY. The flag belongs to child entities and is used to distinguish between accessing them via association or direct access.
CLASS zpru_cl_example_buffer DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
*****
*****
"structure types for input parameters for buffer preparation methods
TYPES: BEGIN OF ts_first_node_keys.
INCLUDE TYPE ts_first_node_db_keys.
TYPES: END OF ts_first_node_keys.
TYPES: BEGIN OF ts_second_node_keys.
INCLUDE TYPE ts_second_node_db_keys.
TYPES: full_key TYPE abap_bool,
END OF ts_second_node_keys.
*****
*****
"table types for input parameters for buffer preparation methods
TYPES tt_first_node_keys TYPE TABLE OF ts_first_node_keys WITH EMPTY KEY.
TYPES tt_second_node_keys TYPE TABLE OF ts_second_node_keys WITH EMPTY KEY.
*****
*****
" buffer preparation methods
CLASS-METHODS prep_first_node_buffer
IMPORTING !keys TYPE tt_first_node_keys.
CLASS-METHODS prep_second_node_buffer
IMPORTING !keys TYPE tt_second_node_keys.
CLASS-METHODS prep_third_node_buffer
IMPORTING !keys TYPE tt_third_node_keys.
CLASS-METHODS prep_fourth_node_buffer
IMPORTING !keys TYPE tt_fourth_node_keys.
ENDCLASS.Root preparation
Let's check code inside the preparation method for root entity.
METHOD prep_first_node_buffer.
DATA lt_keys_2_read LIKE keys.
DATA lt_entity_result TYPE STANDARD TABLE OF zr_pru_1st_node WITH EMPTY KEY.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<ls_key>).
IF line_exists( zpru_cl_example_buffer=>st_first_node[ instance-firstkey = <ls_key>-firstkey ] ).
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_key_2_read>).
<ls_key_2_read>-firstkey = <ls_key>-firstkey.
ENDIF.
ENDLOOP.
IF lt_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_1st_node
FOR ALL ENTRIES IN lt_keys_2_read
WHERE firstkey = lt_keys_2_read-firstkey
INTO TABLE lt_entity_result.
IF sy-subrc = 0.
LOOP AT lt_entity_result ASSIGNING FIELD-SYMBOL(<ls_result>).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_first_node ASSIGNING FIELD-SYMBOL(<ls_buffer>).
<ls_buffer>-instance = <ls_result>.
ENDLOOP.
ENDIF.
ENDIF.
ENDMETHOD.Root preparation logic is the following:
- check if the entry exists in the buffer using the full key
- if yes:
- do nothing
- if no:
- collect keys to select data from the data source
- select data using the full key
- insert entry into the buffer
- if yes:
Checking existence and reading data based on buffer keys, which can be full or partial and depending on whether we are reading a root or child entity, and whether the access is direct or via association. In the case of a root entity, it is always a full key. Even when we try to access the parent node (especially the root node) from a child entity, we use "to-parent" association and provide the full key. I will explain buffer keys in separate chapter.
Child preparation
Let's check code inside the preparation method for child entity. This is where the flag FULL_KEY comes into play.
METHOD prep_second_node_buffer.
DATA lt_full_keys_2_read LIKE keys.
DATA lt_part_keys_2_read LIKE keys.
DATA lt_full_entity_result TYPE STANDARD TABLE OF zr_pru_2nd_node WITH EMPTY KEY.
DATA lt_part_entity_result TYPE STANDARD TABLE OF zr_pru_2nd_node WITH EMPTY KEY.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<ls_key_child>).
IF <ls_key_child>-full_key = abap_true.
IF line_exists( zpru_cl_example_buffer=>st_second_node[ instance-firstkey = <ls_key_child>-firstkey
instance-secondkey = <ls_key_child>-secondkey ] ).
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_full_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_full_key_2_read>).
<ls_full_key_2_read>-firstkey = <ls_key_child>-firstkey.
<ls_full_key_2_read>-secondkey = <ls_key_child>-secondkey.
ENDIF.
ELSE.
IF line_exists( zpru_cl_example_buffer=>st_first_node[ instance-firstkey = <ls_key_child>-firstkey ] )
AND VALUE #( zpru_cl_example_buffer=>st_first_node[ instance-firstkey = <ls_key_child>-firstkey ]-deleted OPTIONAL ) IS NOT INITIAL.
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_part_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_part_key_2_read>).
<ls_part_key_2_read>-firstkey = <ls_key_child>-firstkey.
ENDIF.
ENDIF.
ENDLOOP.
IF lt_full_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_2nd_node
FOR ALL ENTRIES IN _full_keys_2_read
WHERE firstkey = lt_full_keys_2_read-firstkey
AND secondkey = lt_full_keys_2_read-secondkey
INTO TABLE lt_full_entity_result.
IF sy-subrc = 0.
LOOP AT lt_full_entity_result ASSIGNING FIELD-SYMBOL(<ls_result>).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_second_node ASSIGNING FIELD-SYMBOL(<ls_buffer>).
<ls_buffer>-instance = <ls_result>.
ENDLOOP.
ENDIF.
ENDIF.
IF lt_part_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_2nd_node
FOR ALL ENTRIES IN lt_part_keys_2_read
WHERE firstkey = lt_part_keys_2_read-firstkey
INTO TABLE lt_part_entity_result.
IF sy-subrc = 0.
LOOP AT lt_part_entity_result ASSIGNING <ls_result>.
IF NOT line_exists( zpru_cl_example_buffer=>st_second_node[ instance-firstkey = <ls_result>-firstkey instance-secondkey = <ls_result>-secondkey ] ).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_second_node ASSIGNING <ls_buffer>.
<ls_buffer>-instance = <ls_result>.
ENDIF.
ENDLOOP.
ENDIF.
ENDIF.
ENDMETHOD.Let's explore logic.
- check if the key is a full key:
- if it is a full key:
- check if the entry exists in the buffer using the full key:
- if yes:
- do nothing
- if no:
- collect the key to select data from the data source
- select data using the full key
- insert the child entry into the buffer
- if yes:
- check if the entry exists in the buffer using the full key:
- if it is a partial key:
- check if the parent node is marked for deletion in the buffer
- if yes:
- do nothing
- if no:
- collect the key to select data from the data source
- select one or multiple child entities using the partial key
- loop through the selection result:
- check if each processing child entity already exists in the buffer
- if it does not exist: add it into the buffer table
- check if each processing child entity already exists in the buffer
- if yes:
- check if the parent node is marked for deletion in the buffer
- if it is a full key:
You may ask why we check if the parent node, marked for deletion, exists in the buffer in the partial key logic. The answer is that when we mark for deletion parent node, we must mark for deletion all its children (cascading deletion).
This is the only scenario where we can be certain that all children are already present in the buffer. In other cases, we can't guarantee that all children are buffered; for example only four out of five children might currently be loaded.
You may also ask why we check if a child entry exists before inserting it into the buffer table in the partial key logic. Let me give you an example. We have one parent node and four children:
- parent1
- child1
- child2
- child3
- child4
Firstly, you do operation "read child1"; as a result you have child1 inside the buffer table. Then you perform a "read by associations" for the children of parent1.
During child preparation method you will have only the parent key (the partial key logic) and you will select all children of parent1 (child1, child2, child3 and child4). So the next step is to insert all of these children into the buffer table, but child1 is already presented. To avoid duplicates in the buffer table, we check for the existence of the child entity before inserting.
Full Buffer key vs Partial Buffer key.
During checking existence in the buffer or reading data from data source we use either the full key or the partial key of the entity:
- The partial key logic: used when we access the entity via composition. The partial key logic take place when we move from parent to child.
- The full key logic: used when we access the entity directly or by association inversed direction from child to parent.
While maintaining the buffer is primarily required for compositions, it may also be neccessary in cross-BO scenario where external associations are involved.
Impact of key field data type on buffer handling:
- Semantic key (e.g. character, numeric etc. fields): you must handle entire set of the key fields for each level and the set may grow when you are moving dipper in RAP business object tree.
- GUID keys(raw16): you only need to process the only technical GUID field for reading, checking entity in the buffer tables.
The full key representation.
This represents the complete primary key required to identify a specific instance.
| Level | Semantic keys | GUID keys |
| first node | FirstKey | FirstGuid |
| second node | FirstKey, SecondKey | SecondGuid |
| third node | FirstKey, SecondKey, ThirdKey | ThirdGuid |
| fourth node | FirstKey, SecondKey, ThirdKey, FourthKey | FourthGuid |
The partial key representation.
This represents the keys of the imediate parent, used to fetch all associated children.
| Level | Semantic keys | GUID keys |
| first node | N/A | N/A |
| second node | FirstKey | FirstGuid |
| third node | FirstKey, SecondKey | SecondGuid |
| fourth node | FirstKey, SecondKey, ThirdKey | ThirdGuid |
Schematically it can be expressed in the following way:
FULL KEY
NOT GUID
node / key field
1 / 1
2 / 1, 2
3 / 1, 2, 3
4 / 1, 2, 3, 4
GUID
node / key field
1 / 1
2 / 2
3 / 3
4 / 4
PARENT KEY
NOT GUID
node / key field
1 / N/A
2 / 1
3 / 1, 2
4 / 1, 2, 3
GUID
node / key field
1 / N/A
2 / 1
3 / 2
4 / 3 Complex Data source.
In a managed scenario, data source is defined as a persisted table in the BDEF. In an unmanaged scenario, the data source has a complex structure from which you load entries into the buffer and save entries from the buffer. 'Complex' means it can not be reduced to a single database table. It might be a BAPI, an HTTP response, a legacy API class or a combination of them.
This complexity requires choosing an unmanaged implementation. You will also notice that you can not use keyword 'persistent table' in BDEF for unmanaged implementation type. And that is fair; your data source is not a just database table.
METHOD prep_first_node_buffer.
*****
*****
IF lt_keys_2_read IS NOT INITIAL.
" SELECT * FROM zr_pru_1st_node
" CALL EXTERNAL SOURCE VIA HTTP
" CALL BAPI
" CALL LEGACY API
" UPLOAD FILE
" COMBINATION OF THEM AND ETC.
IF sy-subrc = 0.
LOOP AT lt_entity_result ASSIGNING FIELD-SYMBOL(<ls_result>).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_first_node ASSIGNING FIELD-SYMBOL(<ls_buffer>).
<ls_buffer>-instance = <ls_result>.
ENDLOOP.
ENDIF.
ENDIF.
ENDMETHOD.Buffer Components
Buffer flags
Each row of the entity buffer table may contain any flags you require, but the minimum necessary are 'changed' and 'deleted' flags. When you initialize a buffer entry, both flags are false. When you update an entry, you setup the changed flag to true. When you delete an entry, you setup the delete flag to true. Optionally, during deletion, you may set both flags as true, but I don't prefer such way. Finally, in the method SAVE you will sort all buffer entries into two separate internal local tables: one for modifying database, another for deleting from the database.
CLASS zpru_cl_example_buffer DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
*****
*****
TYPES: BEGIN OF ts_first_node,
instance TYPE zr_pru_1st_node,
*****
*****
changed TYPE abap_bool,
deleted TYPE abap_bool,
END OF ts_first_node.
TYPES: BEGIN OF ts_second_node,
instance TYPE zr_pru_2nd_node,
*****
*****
changed TYPE abap_bool,
deleted TYPE abap_bool,
END OF ts_second_node.
*****
*****
ENDCLASS.Buffer additional components
The entity buffer table row may contain components as CID for handling field %CID, and PID for field %PID field in late numbering scenarios. Since I am using a late nubmering scenario, I have added component FINAL_KEY to preserve the result from ADJUST_NUMBERS method.
CLASS zpru_cl_example_buffer DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
*****
*****
TYPES: BEGIN OF ts_first_node,
instance TYPE zr_pru_1st_node,
cid TYPE abp_behv_cid,
pid TYPE abp_behv_pid,
final_key TYPE ts_first_node_db_keys,
*****
*****
END OF ts_first_node.
TYPES: BEGIN OF ts_second_node,
instance TYPE zr_pru_2nd_node,
cid TYPE abp_behv_cid,
pid TYPE abp_behv_pid,
pidparent TYPE abp_behv_pid,
final_key TYPE ts_second_node_db_keys,
*****
*****
END OF ts_second_node.
*****
*****
ENDCLASS.
Generic pattern of usage.
Buffer handled in the following way:
- In transactinal phase in method READ, CREATE, UPDATE, DELETE, RBA AND CBA:
- Prepare buffer using input keys using methods ZPRU_CL_EXAMPLE_BUFFER=>PREP_FIRST_NODE_BUFFER
- read, insert, update, delete(set deleted flag) entry from, in entity buffer table
- In save phase:
- Determine values for entries from entity buffer table in method FINALIZE
- Validate values for entries from entity buffer table in method CHECK_BEFORE_SAVE
- Save entries from entity buffer tables to complex data source in method SAVE
- Clean-up buffer tables in method CLEANUP
Buffer access.
From handling perspective transactinal buffer can be accesed directly and indirectly in your unmanaged implementation during different phase.
Buffer access in Transactional phase.
Basically during transactional phase we can access buffer in two flawors in ABP class:
- Direct access is used in such methods like read, read by association, create by association, create, update, delete(standard operations).
- Indirect access is used in such methods for instance feature, instance authorization, actions, functions and etc. via EML read, modify(non-standard operations)
Buffer access in Save phase
During save phase we access data directly from buffer, the main reason is that methods FINALIZE, CHECK_BEFORE_SAVE, ADJUST_NUMBERS, SAVE, CLEANUP and CLEANUP_FINALIZE are not provided with input entity keys. The only source of eventual keys to process is the buffer tables itself. Also we can use EML to read data taking keys from buffer tables.
Types of access in save phase:
- predominantly direct access in methods FINALIZE, CHECK_BEFORE_SAVE, ADJUST_NUMBERS, SAVE, CLEANUP and CLEANUP_FINALIZE.
- indirect access via EML read.
" No input keys
" To find out what to process we must have a look in the buffer.
" Finalize execute determinations, be aware that determiantion for draft
" and active instances are different things. Finalize is mainly
" about active data
methods FINALIZE
changing
!FAILED type DATA
!REPORTED type DATA .
" No input keys
" This method for validation and again mainly for active data.
methods CHECK_BEFORE_SAVE
changing
!FAILED type DATA
!REPORTED type DATA .
" input keys
" This method will save active data to source of active data
" Usually the data source is complex, not just one table or even two tables
methods SAVE
changing
!REPORTED type DATA optional
!FAILED type DATA optional .Transactional buffer vs Draft buffer.
If you business object has draft capabilities, don't conflate transactional buffer implemented by developer and draft buffer handled by managed runtime. They live in parallel and sometimes need to be synchronized via additional implementation for EDIT, ACTIVATE, DISCARD, RESUME. All in all draft processing is carried on by managed runtime despite that you have unmanaged implementation of business object.
Operation aggregation.
Transactional buffer must handle operation aggregation. Operation aggregation means that user can create, update and update one more time, execute action in the same LUW, more over even in one EML statement. One more prominant example of operation aggregation is when user create, delete and then create again entry for RAP business object.
*All CRUD operations must support aggreggation of operations.
*Just for your undestanding there are a lot possible sequences like:
*1. C 2. U 3. D 4. C + U 5. C + D 6. D + C 7. C + U + D 8. U + D + C and etc.
*The following level of complexity is that operations can be repeate multiple *times for the same instance(set of key fields)
*C + C + U + U + U + D and etc.
*There are more caviats that you can think, eg.
*C1 + D + C2 => create with some particalar fields, then mark it as deleted
*and it is still in our buffer, then create again with the same key but
*another set of data fields
Delete vs Discard vs Remove entry from buffer.
So let's define couple moments:
- Delete means to set flag 'deleted' in method DELETE of ABP pool class. Real deletion happens in method SAVE, based on this flag.
- Discard - basically the same as delete but it is handled by draft runtime and is provided out-of-box. Developer isn't responsible for discard implementation. We only can add additional implementation to provide additional logic for the method DISCARD.
- Remove - sometimes can happend, e.g. in the method CLEANUP or in the method CREATE during attempt to recreate entry with the same keys as previously marked as deleted(delete then create again).
Unmanaged Transactional Buffer Blueprint
At last, we can conclude our research with the publication of the code of the buffer class, the so called unmanaged trasnactional buffer blueprint. You are welcom to utilize and adapt this code for your specific requirements.
CLASS zpru_cl_example_buffer DEFINITION
PUBLIC FINAL
CREATE PUBLIC.
PUBLIC SECTION.
TYPES: BEGIN OF ts_first_node_db_keys,
firstkey TYPE zr_pru_1st_node-firstkey,
END OF ts_first_node_db_keys.
TYPES: BEGIN OF ts_second_node_db_keys,
firstkey TYPE zr_pru_2nd_node-firstkey,
secondkey TYPE zr_pru_2nd_node-secondkey,
END OF ts_second_node_db_keys.
TYPES: BEGIN OF ts_third_node_db_keys,
firstkey TYPE zr_pru_3rd_node-firstkey,
secondkey TYPE zr_pru_3rd_node-secondkey,
thirdkey TYPE zr_pru_3rd_node-thirdkey,
END OF ts_third_node_db_keys.
TYPES: BEGIN OF ts_fourth_node_db_keys,
firstkey TYPE zr_pru_4th_node-firstkey,
secondkey TYPE zr_pru_4th_node-secondkey,
thirdkey TYPE zr_pru_4th_node-thirdkey,
fourthkey TYPE zr_pru_4th_node-fourthkey,
END OF ts_fourth_node_db_keys.
TYPES: BEGIN OF ts_first_node,
instance TYPE zr_pru_1st_node,
cid TYPE abp_behv_cid,
pid TYPE abp_behv_pid,
final_key TYPE ts_first_node_db_keys,
changed TYPE abap_bool,
deleted TYPE abap_bool,
END OF ts_first_node.
TYPES: BEGIN OF ts_second_node,
instance TYPE zr_pru_2nd_node,
cid TYPE abp_behv_cid,
pid TYPE abp_behv_pid,
pidparent TYPE abp_behv_pid,
final_key TYPE ts_second_node_db_keys,
changed TYPE abap_bool,
deleted TYPE abap_bool,
END OF ts_second_node.
TYPES: BEGIN OF ts_third_node,
instance TYPE zr_pru_3rd_node,
cid TYPE abp_behv_cid,
pid TYPE abp_behv_pid,
pidparent TYPE abp_behv_pid,
final_key TYPE ts_third_node_db_keys,
changed TYPE abap_bool,
deleted TYPE abap_bool,
END OF ts_third_node.
TYPES: BEGIN OF ts_fourth_node,
instance TYPE zr_pru_4th_node,
pid TYPE abp_behv_pid,
pidparent TYPE abp_behv_pid,
final_key TYPE ts_fourth_node_db_keys,
changed TYPE abap_bool,
deleted TYPE abap_bool,
END OF ts_fourth_node.
TYPES tt_first_node TYPE TABLE OF ts_first_node WITH EMPTY KEY.
TYPES tt_second_node TYPE TABLE OF ts_second_node WITH EMPTY KEY.
TYPES tt_third_node TYPE TABLE OF ts_third_node WITH EMPTY KEY.
TYPES tt_fourth_node TYPE TABLE OF ts_fourth_node WITH EMPTY KEY.
CLASS-DATA st_first_node TYPE tt_first_node.
CLASS-DATA st_second_node TYPE tt_second_node.
CLASS-DATA st_third_node TYPE tt_third_node.
CLASS-DATA st_fourth_node TYPE tt_fourth_node.
TYPES: BEGIN OF ts_first_node_keys.
INCLUDE TYPE ts_first_node_db_keys.
TYPES: END OF ts_first_node_keys.
TYPES: BEGIN OF ts_second_node_keys.
INCLUDE TYPE ts_second_node_db_keys.
TYPES: full_key TYPE abap_bool,
END OF ts_second_node_keys.
TYPES: BEGIN OF ts_third_node_keys.
INCLUDE TYPE ts_third_node_db_keys.
TYPES: full_key TYPE abap_bool,
END OF ts_third_node_keys.
TYPES: BEGIN OF ts_fourth_node_keys.
INCLUDE TYPE ts_fourth_node_db_keys.
TYPES: full_key TYPE abap_bool,
END OF ts_fourth_node_keys.
TYPES tt_first_node_keys TYPE TABLE OF ts_first_node_keys WITH EMPTY KEY.
TYPES tt_second_node_keys TYPE TABLE OF ts_second_node_keys WITH EMPTY KEY.
TYPES tt_third_node_keys TYPE TABLE OF ts_third_node_keys WITH EMPTY KEY.
TYPES tt_fourth_node_keys TYPE TABLE OF ts_fourth_node_keys WITH EMPTY KEY.
CLASS-METHODS prep_first_node_buffer
IMPORTING !keys TYPE tt_first_node_keys.
CLASS-METHODS prep_second_node_buffer
IMPORTING !keys TYPE tt_second_node_keys.
CLASS-METHODS prep_third_node_buffer
IMPORTING !keys TYPE tt_third_node_keys.
CLASS-METHODS prep_fourth_node_buffer
IMPORTING !keys TYPE tt_fourth_node_keys.
ENDCLASS.
CLASS zpru_cl_example_buffer IMPLEMENTATION.
METHOD prep_first_node_buffer.
DATA lt_keys_2_read LIKE keys.
DATA lt_entity_result TYPE STANDARD TABLE OF zr_pru_1st_node WITH EMPTY KEY.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<ls_key>).
IF line_exists( zpru_cl_example_buffer=>st_first_node[ instance-firstkey = <ls_key>-firstkey ] ).
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_key_2_read>).
<ls_key_2_read>-firstkey = <ls_key>-firstkey.
ENDIF.
ENDLOOP.
IF lt_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_1st_node
FOR ALL ENTRIES IN lt_keys_2_read
WHERE firstkey = lt_keys_2_read-firstkey
INTO TABLE lt_entity_result.
IF sy-subrc = 0.
LOOP AT lt_entity_result ASSIGNING FIELD-SYMBOL(<ls_result>).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_first_node ASSIGNING FIELD-SYMBOL(<ls_buffer>).
<ls_buffer>-instance = <ls_result>.
ENDLOOP.
ENDIF.
ENDIF.
ENDMETHOD.
METHOD prep_second_node_buffer.
DATA lt_full_keys_2_read LIKE keys.
DATA lt_part_keys_2_read LIKE keys.
DATA lt_full_entity_result TYPE STANDARD TABLE OF zr_pru_2nd_node WITH EMPTY KEY.
DATA lt_part_entity_result TYPE STANDARD TABLE OF zr_pru_2nd_node WITH EMPTY KEY.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<ls_key_child>).
IF <ls_key_child>-full_key = abap_true.
IF line_exists( zpru_cl_example_buffer=>st_second_node[ instance-firstkey = <ls_key_child>-firstkey
instance-secondkey = <ls_key_child>-secondkey ] ).
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_full_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_full_key_2_read>).
<ls_full_key_2_read>-firstkey = <ls_key_child>-firstkey.
<ls_full_key_2_read>-secondkey = <ls_key_child>-secondkey.
ENDIF.
ELSE.
IF line_exists( zpru_cl_example_buffer=>st_first_node[ instance-firstkey = <ls_key_child>-firstkey ] )
AND VALUE #( zpru_cl_example_buffer=>st_first_node[ instance-firstkey = <ls_key_child>-firstkey ]-deleted OPTIONAL ) IS NOT INITIAL.
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_part_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_part_key_2_read>).
<ls_part_key_2_read>-firstkey = <ls_key_child>-firstkey.
ENDIF.
ENDIF.
ENDLOOP.
IF lt_full_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_2nd_node
FOR ALL ENTRIES IN lt_full_keys_2_read
WHERE firstkey = lt_full_keys_2_read-firstkey
AND secondkey = lt_full_keys_2_read-secondkey
INTO TABLE lt_full_entity_result.
IF sy-subrc = 0.
LOOP AT lt_full_entity_result ASSIGNING FIELD-SYMBOL(<ls_result>).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_second_node ASSIGNING FIELD-SYMBOL(<ls_buffer>).
<ls_buffer>-instance = <ls_result>.
ENDLOOP.
ENDIF.
ENDIF.
IF lt_part_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_2nd_node
FOR ALL ENTRIES IN lt_part_keys_2_read
WHERE firstkey = lt_part_keys_2_read-firstkey
INTO TABLE lt_part_entity_result.
IF sy-subrc = 0.
LOOP AT lt_part_entity_result ASSIGNING <ls_result>.
IF NOT line_exists( zpru_cl_example_buffer=>st_second_node[ instance-firstkey = <ls_result>-firstkey
instance-secondkey = <ls_result>-secondkey ] ).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_second_node ASSIGNING <ls_buffer>.
<ls_buffer>-instance = <ls_result>.
ENDIF.
ENDLOOP.
ENDIF.
ENDIF.
ENDMETHOD.
METHOD prep_third_node_buffer.
DATA lt_full_keys_2_read LIKE keys.
DATA lt_part_keys_2_read LIKE keys.
DATA lt_full_entity_result TYPE STANDARD TABLE OF zr_pru_3rd_node WITH EMPTY KEY.
DATA lt_part_entity_result TYPE STANDARD TABLE OF zr_pru_3rd_node WITH EMPTY KEY.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<ls_key_child>).
IF <ls_key_child>-full_key = abap_true.
IF line_exists( zpru_cl_example_buffer=>st_third_node[ instance-firstkey = <ls_key_child>-firstkey
instance-secondkey = <ls_key_child>-secondkey
instance-thirdkey = <ls_key_child>-thirdkey ] ).
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_full_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_full_key_2_read>).
<ls_full_key_2_read>-firstkey = <ls_key_child>-firstkey.
<ls_full_key_2_read>-secondkey = <ls_key_child>-secondkey.
<ls_full_key_2_read>-thirdkey = <ls_key_child>-thirdkey.
ENDIF.
ELSE.
IF line_exists( zpru_cl_example_buffer=>st_second_node[ instance-firstkey = <ls_key_child>-firstkey
instance-secondkey = <ls_key_child>-secondkey ] )
AND VALUE #( zpru_cl_example_buffer=>st_second_node[ instance-firstkey = <ls_key_child>-firstkey
instance-secondkey = <ls_key_child>-secondkey ]-deleted OPTIONAL ) IS NOT INITIAL.
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_part_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_part_key_2_read>).
<ls_part_key_2_read>-firstkey = <ls_key_child>-firstkey.
ENDIF.
ENDIF.
ENDLOOP.
IF lt_full_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_3rd_node
FOR ALL ENTRIES IN _full_keys_2_read
WHERE firstkey = lt_full_keys_2_read-firstkey
AND secondkey = lt_full_keys_2_read-secondkey
AND thirdkey = lt_full_keys_2_read-thirdkey
INTO TABLE lt_full_entity_result.
IF sy-subrc = 0.
LOOP AT lt_full_entity_result ASSIGNING FIELD-SYMBOL(<ls_result>).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_third_node ASSIGNING FIELD-SYMBOL(<ls_buffer>).
<ls_buffer>-instance = <ls_result>.
ENDLOOP.
ENDIF.
ENDIF.
IF lt_part_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_3rd_node
FOR ALL ENTRIES IN lt_part_keys_2_read
WHERE firstkey = lt_part_keys_2_read-firstkey
AND secondkey = lt_part_keys_2_read-secondkey
INTO TABLE lt_part_entity_result.
IF sy-subrc = 0.
LOOP AT lt_part_entity_result ASSIGNING <ls_result>.
IF NOT line_exists( zpru_cl_example_buffer=>st_third_node[ instance-firstkey = <ls_result>-firstkey
instance-secondkey = <ls_result>-secondkey
instance-thirdkey = <ls_result>-thirdkey ] ).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_third_node ASSIGNING <ls_buffer>.
<ls_buffer>-instance = <ls_result>.
ENDIF.
ENDLOOP.
ENDIF.
ENDIF.
ENDMETHOD.
METHOD prep_fourth_node_buffer.
DATA lt_full_keys_2_read LIKE keys.
DATA lt_part_keys_2_read LIKE keys.
DATA lt_full_entity_result TYPE STANDARD TABLE OF zr_pru_4th_node WITH EMPTY KEY.
DATA lt_part_entity_result TYPE STANDARD TABLE OF zr_pru_4th_node WITH EMPTY KEY.
LOOP AT keys ASSIGNING FIELD-SYMBOL(<ls_key_child>).
IF <ls_key_child>-full_key = abap_true.
IF line_exists( zpru_cl_example_buffer=>st_fourth_node[ instance-firstkey = <ls_key_child>-firstkey
instance-secondkey = <ls_key_child>-secondkey
instance-thirdkey = <ls_key_child>-thirdkey
instance-fourthkey = <ls_key_child>-fourthkey ] ).
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_full_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_full_key_2_read>).
<ls_full_key_2_read>-firstkey = <ls_key_child>-firstkey.
<ls_full_key_2_read>-secondkey = <ls_key_child>-secondkey.
<ls_full_key_2_read>-thirdkey = <ls_key_child>-thirdkey.
<ls_full_key_2_read>-fourthkey = <ls_key_child>-fourthkey.
ENDIF.
ELSE.
IF line_exists( zpru_cl_example_buffer=>st_third_node[ instance-firstkey = <ls_key_child>-firstkey
instance-secondkey = <ls_key_child>-secondkey
instance-thirdkey = <ls_key_child>-thirdkey ] )
AND VALUE #( zpru_cl_example_buffer=>st_third_node[ instance-firstkey = <ls_key_child>-firstkey
instance-secondkey = <ls_key_child>-secondkey
instance-thirdkey = <ls_key_child>-thirdkey ]-deleted OPTIONAL ) IS NOT INITIAL.
CONTINUE.
ELSE.
APPEND INITIAL LINE TO lt_part_keys_2_read ASSIGNING FIELD-SYMBOL(<ls_part_key_2_read>).
<ls_part_key_2_read>-firstkey = <ls_key_child>-firstkey.
ENDIF.
ENDIF.
ENDLOOP.
IF lt_full_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_4th_node
FOR ALL ENTRIES IN lt_full_keys_2_read
WHERE firstkey = lt_full_keys_2_read-firstkey
AND secondkey = lt_full_keys_2_read-secondkey
AND thirdkey = lt_full_keys_2_read-thirdkey
AND fourthkey = lt_full_keys_2_read-fourthkey
INTO TABLE lt_full_entity_result.
IF sy-subrc = 0.
LOOP AT lt_full_entity_result ASSIGNING FIELD-SYMBOL(<ls_result>).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_fourth_node ASSIGNING FIELD-SYMBOL(<ls_buffer>).
<ls_buffer>-instance = <ls_result>.
ENDLOOP.
ENDIF.
ENDIF.
IF lt_part_keys_2_read IS NOT INITIAL.
SELECT * FROM zr_pru_4th_node
FOR ALL ENTRIES IN lt_part_keys_2_read
WHERE firstkey = lt_part_keys_2_read-firstkey
AND secondkey = lt_part_keys_2_read-secondkey
AND thirdkey = lt_part_keys_2_read-thirdkey
INTO TABLE lt_part_entity_result.
IF sy-subrc = 0.
LOOP AT lt_part_entity_result ASSIGNING <ls_result>.
IF NOT line_exists( zpru_cl_example_buffer=>st_fourth_node[ instance-firstkey = <ls_result>-firstkey
instance-secondkey = <ls_result>-secondkey
instance-thirdkey = <ls_result>-thirdkey
instance-fourthkey = <ls_result>-fourthkey ] ).
APPEND INITIAL LINE TO zpru_cl_example_buffer=>st_fourth_node ASSIGNING <ls_buffer>.
<ls_buffer>-instance = <ls_result>.
ENDIF.
ENDLOOP.
ENDIF.
ENDIF.
ENDMETHOD.
ENDCLASS.