Quantcast
Channel: ABAP Development
Viewing all articles
Browse latest Browse all 948

Persistent classes: revival of a spirit. Query by range.

$
0
0

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.

 

If I'll find some supporters then we can create some open-source simple project as I have more interesting ideas about persistent classes such as:

 

  • get_structure method instead of multiple calls of get_ methods through serialization + XSLT tranformation.
  • query hash ( keep result for generic queries) using query structure serialization + hash sum - now it does select every time

 

But that will be described in next posts.

 

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.

Viewing all articles
Browse latest Browse all 948

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>