Dear ABAPers,

Probably most of you are aware of persistent classes, meanwhile I don’t know many developers who’re actively using them.

Moreover, I just get a response from SAP:

I would propose to avoid the usage of

persisten classes in projects where the underlying structure of the

persistent class is not stable. Generating new persitent classes

is easy, but maintenance of excisting persitent classes could led in

high effort. For this reason I would propose to take the future

maintenace effort into account when you decide to use or not to use

persistent classes.

After spending 2 years on standard TR-TM solution support, that is persistent classes based, I don’t think it’s such a bad thing.

What I want to reach with my blog is to present my personal ideas about how we can make usage of persistent clasess more attractive.

Let’s speak today about query service:

So every persistent class has embedded interface if_os_ca_persistency. Details are here:

Query Service Components – ABAP – Object Services – SAP Library

This interface has very interesting method: get_persistent_by_query

In fact once generating a class for a table we automtically have query service for it – that sounds promising. Let’s go to example:

Here is a code from a standard DEMO_QUERY_SERVICE program.


agent = ca_spfli_persistent=>agent.
    TRY.
        query_manager = cl_os_system=>get_query_manager( ).
        query = query_manager->create_query(
                  i_filter  = `AIRPFROM = PAR1 AND AIRPTO = PAR2` ).
        connections =
          agent->if_os_ca_persistency~get_persistent_by_query(
                   i_query   = query
                   i_par1    = airpfrom
                   i_par2    = airpto ).
        LOOP AT connections ASSIGNING FIELD-SYMBOL(<connection>).
          connection = CAST #( <connection> ).
          result-carrid = connection->get_carrid( ).
          result-connid = connection->get_connid( ).
          APPEND result TO results.
        ENDLOOP.
        cl_demo_output=>display( results ).
      CATCH cx_root INTO exc.
        cl_demo_output=>display( exc->get_text( ) ).
    ENDTRY.



What we can see here is that SAP developers suggest us to use a generic request from a string.

Which critical things I see in this example:

  • we have only 3 parameters
  • there is no reference to DDIC structure that means where-used-list will not work for this statement
  • select-options and ranges are not supported

So after that I decided that if we transform the same example into the code like this we  can make things simplier:


REPORT ZDEMO_QUERY_SERVICE.
tables: spfli.
parameters:
  p_from type spfli-airpfrom,
  p_to type spfli-airpto.
select-options:
  so_carid for spfli-carrid.
start-of-selection.
  types: begin of query_ts,
    airpfrom type spfli-airpfrom,
    airpto type spfli-airpto,
    carrid type range of spfli-carrid,
  end of query_ts.
  data(connections) = zcl_os_api=>select_by_query(
    exporting
      io_agent     =  ca_spfli_persistent=>agent   " Class-Specific Persistence Interface
      is_selection = value query_ts(
        airpfrom = p_from
        airpto = p_to
        carrid = so_carid[]
      )
    ).


As you can see from the example I represented query not like a single string, but as local variable of structure type where fields have same names as in source table. Moreover, to support multiple selection, you can define parameter as a range (CARRID).

To perform range selection I decided to convert range to a set of OR statements ( SIGN = ‘I’ ) + set of AND statements (SIGN = ‘E’).

This simple class now let me easily generate simple classes for selection.

1) Generate persistent class

2) Define local variable for query

3) Call query with agent and query structure.

The provided class is just a prototype. If you wish – you can copy it and try to use it.

Please read the continue in next posts:

The most actual version of a class is here: ZCL_OS_API

Enjoy =)


class ZCL_OS_API definition
  public
  abstract
  final
  create public .
public section.
  class-methods SELECT_BY_QUERY
    importing
      !IO_AGENT type ref to IF_OS_CA_PERSISTENCY
      !IS_SELECTION type ANY
    changing
      !CO_TYPE type ref to CL_ABAP_STRUCTDESCR optional
    returning
      value(RT_RESULT) type OSREFTAB .
protected section.
private section.
  types:
    begin of range_ts,
            sign type c length 1,
            option type c length 2,
            low type string,
            high type string,
          end of range_ts .
  class-methods GET_QUERY_RANGE_VALUE
    importing
      !IS_RANGE type RANGE_TS
      !IO_EXPR type ref to IF_OS_QUERY_EXPR_FACTORY
      !IV_NAME type STRING
    returning
      value(RO_EXPR) type ref to IF_OS_QUERY_FILTER_EXPR .
  class-methods GET_QUERY_RANGE
    importing
      !IT_RANGE type TABLE
      !IV_NAME type STRING
      !IO_EXPR type ref to IF_OS_QUERY_EXPR_FACTORY
    returning
      value(RO_EXPR) type ref to IF_OS_QUERY_FILTER_EXPR .
  type-pools ABAP .
  class-methods IS_RANGE
    importing
      !IO_TYPE type ref to CL_ABAP_TABLEDESCR
    returning
      value(RV_RANGE) type ABAP_BOOL .
  class-methods GET_QUERY
    importing
      !IS_SELECTION type ANY
    changing
      !CO_TYPE type ref to CL_ABAP_STRUCTDESCR optional
    returning
      value(RO_QUERY) type ref to IF_OS_QUERY .
ENDCLASS.
CLASS ZCL_OS_API IMPLEMENTATION.
  method get_query.
    data(lo_query) = cl_os_query_manager=>get_query_manager( )->if_os_query_manager~create_query( ).
    data(lo_expr) = lo_query->get_expr_factory( ).
    if co_type is not bound.
      try.
          co_type = cast #( cl_abap_typedescr=>describe_by_data( is_selection  ) ).
        catch cx_sy_move_cast_error.
          " ToDo: message
          return.
      endtry.
    endif.
    data: lt_and type table of ref to if_os_query_filter_expr.
    " for each selection criteria
    loop at co_type->get_included_view(
*            p_level =
        ) into data(ls_view).
      " parameter or range?
      case ls_view-type->kind.
        " parameter
        when ls_view-type->kind_elem.
          field-symbols: <lv_component> type any.
          unassign <lv_component>.
          assign component ls_view-name of structure is_selection to <lv_component>.
          check <lv_component> is assigned.
          try.
              " goes to and condition
              append
                lo_expr->create_operator_expr(
                    i_attr1                     = ls_view-name
                  i_operator                  = 'EQ'
                  i_val                       = conv #( <lv_component> )
                ) to lt_and.
            catch cx_os_query_expr_fact_error.    "
          endtry.
        when ls_view-type->kind_table.
          " check: is range?
          check is_range( cast #( ls_view-type ) ) eq abap_true.
          " must be not initial
          field-symbols: <lt_range> type table.
          assign component ls_view-name of structure is_selection to <lt_range>.
          check <lt_range> is assigned.
          check <lt_range> is not initial.
          " goes to and condition
          append get_query_range(
            iv_name = ls_view-name
            it_range = <lt_range>
            io_expr = lo_expr )  to lt_and.
      endcase.
    endloop.
    " build and conditions
    loop at lt_and into data(lo_and).
      if sy-tabix eq 1.
        data(lo_filter) = lt_and[ 1 ].
      else.
        lo_filter = lo_expr->create_and_expr(
          exporting
            i_expr1 = lo_filter
            i_expr2 = lo_and
        ).
      endif.
    endloop.
    lo_query->set_filter_expr( lo_filter ).
    ro_query = lo_query.
  endmethod.
  method get_query_range.
    data: lt_and type table of ref to if_os_query_filter_expr,
          lt_or type table of ref to if_os_query_filter_expr.
    data: ls_range type range_ts.
    " .. for each range value
    loop at it_range assigning field-symbol(<ls_range>).
      move-corresponding exact <ls_range> to ls_range.
      " E = AND, I = OR
      case ls_range-sign.
        when 'E'.
          append io_expr->create_not_expr(
            get_query_range_value(
             is_range = ls_range
             io_expr = io_expr
             iv_name = iv_name ) )  to lt_and..
        when 'I'.
          append get_query_range_value(
            is_range = ls_range
            io_expr = io_expr
            iv_name = iv_name ) to lt_or.
      endcase.
    endloop.
    " First of all combine all OR in to a single expression
    loop at lt_or into data(lo_or).
      if sy-tabix eq 1.
        data(lo_filter_or) = lt_or[ 1 ].
      else.
        lo_filter_or = io_expr->create_or_expr(
          exporting
            i_expr1 = lo_filter_or
            i_expr2 = lo_or
        ).
      endif.
    endloop.
    " make all or statements as one of ANDs
    append lo_filter_or to lt_and.
    loop at lt_and into data(lo_and).
      if sy-tabix eq 1.
        ro_expr = lt_and[ 1 ].
      else.
        ro_expr = io_expr->create_and_expr(
          exporting
            i_expr1 = ro_expr
            i_expr2 = lo_and
        ).
      endif.
    endloop.
  endmethod.
  method get_query_range_value.
    try.
        case is_range-option.
          " is operator
          when
            'EQ' or
            'NE' or
            'LE' or
            'LT' or
            'GE' or
            'GT'
           .
            ro_expr = io_expr->create_operator_expr(
                  i_attr1                     = iv_name
                  i_operator                  = conv #( is_range-option )
                  i_val                       = is_range-low
              ).
          " is mask
          when 'CP'.
            data(lv_pattern) = is_range-low.
            replace all occurrences of '*' in lv_pattern with '%'.
            ro_expr = io_expr->create_like_expr(
              i_attr                      = iv_name
              i_pattern                   = lv_pattern
*              i_not                       = OSCON_FALSE
          ).
          " is mask with not
          when 'NP'.
            lv_pattern = is_range-low.
            replace all occurrences of '*' in lv_pattern with '%'.
            ro_expr = io_expr->create_like_expr(
              i_attr                      = iv_name
              i_pattern                   = lv_pattern
              i_not                       = oscon_true
          ).
*            when 'BT'.
          when others.
            " not supported
        endcase.
      catch cx_os_query_expr_fact_error.  "
    endtry.
  endmethod.
  method IS_RANGE.
    CHECK io_type->table_kind eq io_type->tablekind_std AND
          io_type->key_defkind eq io_type->KEYDEFKIND_DEFAULT AND
          io_type->key eq value ABAP_KEYDESCR_TAB(
           ( name = 'SIGN')
           ( name = 'OPTION')
           ( name = 'LOW')
           ( name = 'HIGH') ).
    rv_range = abap_true.
  endmethod.
  method select_by_query.
    " check agent
    check io_agent is bound.
    try.
        " get result by using generated method
        rt_result = io_agent->get_persistent_by_query(
          exporting
            " create query by selection criteria
            i_query = get_query(
              exporting
                is_selection =  is_selection   " Must be structure
              changing
                co_type      = co_type     " Runtime Type Services
            )   " Query
        ).
      catch cx_os_object_not_found.    "
      catch cx_os_query_error.    "
    endtry.
  endmethod.
ENDCLASS.

To report this post you need to login first.

3 Comments

You must be Logged on to comment or reply to a post.

  1. Rachid Roughi

    – “I would propose to avoid the usage of persisten classes in projects where the underlying structure of the persistent class is not stable. 😀

    Personally, I used Persistence Classes Services during 4 months for a project and I definitely leave behind me this framework when I saw that it was not possible to request your database with a Select-Option condition. 🙁

    The framwork has been improved?

    (0) 
    1. Petr Plenkov Post author

      Actually it’s possible.

      However range should be normalized to a combination of ORs, ANDs and NOTs.

      In fact if you do range by query and check  the execution plan – the system transforms IN range statement to such a form.

      (0) 
  2. Valentin Huber

    Hi Petr,

    You mention “Which critical things I see in this example: … we have only 3 parameters.”

    It’s possible to use more than 3 parameters by using

    DATA(lt_result) = /hrfe/ca_=>agent->if_os_ca_persistency~get_persistent_by_query(
    
         i_query = cl_os_system=>get_query_manager( )->create_query( i_filter = '... = PAR1 AND ... = PAR2 AND ... = PAR3 AND ... = PAR4'
                                                                     i_parameters = 'PAR1 PAR2 PAR3 PAR4' )
         i_parameter_tab = lt_parameter_tab ).
    

    where lt_parameter_tab is a table of references to data elements with (at least) 4 entries.

    Kind regards,

    Valentin

    (0) 

Leave a Reply