Friday, December 16, 2011

SAP ABAP Dynamic assign: how to change data you should not change


Hi Folks!
I'll try to post every day a small pearl in ABAP and SAP HCM.
Today i'd like to focus on a very annoying thing: whenever you need some data in a user exit typically you don't have it in the BADI interface or in the CMOD function module interface.
So what to do? What are you options?
  1. Modify the standard 
    • Rather undesirable. Increases maintainance costs when you upgrade or apply patches to the systems.
  1. EXPORT TO MEMORY
    • Often used, but in a concurrent environmenti it is highly dangerous, unless one understands exactly the scoping of the abap memory (Anyone interestd? leave a comment if you wish a post on that topic). In addition it used to cause performance problems.
  2. Open an OSS and ask SAP to add the data to the interface
    • Sometimes works, but it takes a lot of time, and typically one does not have time. It's just when you go to renew your passport. You realize that it  must be renewed only when you need it, (urgently)
  3. Save to a DB table
    • Might be a solution, also affected by performance and concurrency issues. Concurrency issues might be mitigated by the use of a custom cluster (want a post on that? leave a comment if you wish.)
  4. anymore suggestions?
One really nice way to solve the problem is to use the dynamic assign feature in ABAP
SAP says that this language feature can only be used internally, and that it is not guaranteed to work in the future. But it is there since ages. And we used it very often over the years without having any problem ever.

You can put the name of the field you are interested in in a sting by using the syntax

'(PROGRAM NAME)FIELD'

For example to get a pointer (field symbol) pointing to the table BT in the RPCALCX0 you can use the following

str = '(RPCALCX0)BT[]'

Afterwards you assign the string to the field symbols

FIELD-SYMBOLS <fs> type standard table of my_line.
*Or whaterer table type
assign (str) to <fs> .
finally you can access the field:

Loop at  <fs> into l_line.
 ...
endloop.

the great thing is that you can assign ANY data in the call stack!!
So fro example if RPCALCX0 is calling a funciotn in a funcion group which calls a class, wich calls back to another program then you can read or change data in any of the programs in the call stack, this means in any of the programs you run through in the call chain.

Recommendations (common sense but anyway worth writing down)
  1. Never change the contents of data not in your scope, unless you really know what you are doing.
  2. Documento what you do. Leave traces of your approach in the application blueprint or wahtever techincal documentation.
  3. Always check the return values of your field symbols after assigning: your piece of code might be called from a different program without you knowing, and the call stack might be different, and your assign fail.
  4. Always handle all errors in your coding, making them visibile.
Here is a rela life coding example showing how you can access payroll (rpcalcx0 or whatever payroll) data fron the international RDTAB function via the RDTAB custom class in table T5ITIE/T5ITID
I will write a post on that topic to better explain its working.

METHOD if_hrpadit_table_reader_py~read_table .


  DATA l_str TYPE char30.
  DATA l_str2 TYPE char30.
  DATA l_str3 TYPE char30.
  DATA l_imp TYPE maxbt.
  DATA l_num TYPE pranz.
  DATA ls_pa0306 TYPE pa0306.
  DATA ls_var TYPE hrvar.

  FIELD-SYMBOLS: <res> TYPE ANY.
  CREATE DATA a_result TYPE pranz.
  ASSIGN a_result->* TO
<res>.
 
<res> = 0.

  l_str = '(RPCALCI0)P0009'.
  l_str2 = '(RPCALCI0)VAR[]'.
  l_str3 = '(RPCALCI0)BT'.

  FIELD-SYMBOLS:
<p0009>  TYPE p0009.
  TYPES: t_hrvar TYPE STANDARD TABLE OF hrvar.
  FIELD-SYMBOLS: <var>  TYPE t_hrvar.
  FIELD-SYMBOLS: 
<bt> TYPE pc209.

  ASSIGN (l_str) TO
<p0009>.

  IF sy-subrc NE 0.
 
<res> = 1. "KO
    EXIT.
* error.
  ENDIF.

  ASSIGN (l_str2) TO
<var>.

  IF sy-subrc NE 0.
 
<res>  = 1. "KO
    EXIT.
* error.
  ENDIF.

  ASSIGN (l_str3) TO
<bt>.

  IF sy-subrc NE 0.
 
<res>  = 1. "KO
    EXIT.
* error.
  ENDIF.

  IF
<p0009>-subty EQ 'MY'.

    DELETE WHERE lgart = 'Syyy'.
    ls_var-lgart = 'SETF'.
    ls_var-betpe = ls_pa0306-setfr.
    ls_var-anzhl = ls_pa0306-setfr.
    ls_var-betrg = ls_pa0306-setfr.
    ls_var-rte_curr = 'EUR'.
    ls_var-amt_curr = 'EUR'.
    APPEND ls_var TO
<var>.

  ENDIF.


  IF
<bt>-subty EQ 'ZZ' OR <bt>-subty EQ 'TT'.

    
<bt>-stras  = ls_pa0021-address.
    
<bt>-bkort  = ls_pa0021-town.

   ENDIF.

ENDMETHOD.