Skip to Content

Welcome back to our course on BPath. As you may remember from the first lesson (BPath. Lesson 01: What is BPath?) we introduced BPath and we discovered the first easy BPath statements.

Now we face the question, how our BPath statements are included into our ABAP environment. The following code example coding should illustrate the ways to access BOL with BPath.

At first we need a Business Collection where we start from:

DATA lv_compset type CRMT_GENIL_APPL.
DATA lv_bol_core TYPE REF TO CL_CRM_BOL_CORE.
DATA lv_dyn_query TYPE REF TO CL_CRM_BOL_DQUERY_SERVICE.
DATA lv_result type ref to IF_BOL_ENTITY_COL.

lv_compset = 'UIF'.
lv_bol_core = cl_crm_bol_core=>get_instance( ).
lv_bol_core->start_up( lv_compset ).
lv_dyn_query = cl_crm_bol_dquery_service=>get_instance(
'UIFAdvSearchFlight' ).
lv_dyn_query->set_property(
iv_attr_name = 'MAX_HITS' iv_value = '5' ).
lv_result = lv_dyn_query->get_query_result( ).

At the end of this code fragment, we have a Business Collection which should contain 5 query result objects. Unfortunately BPath does not work on Business Collection directly, so we have to convert it to a Business Collection of uniform type first:

DATA lv_result2 type ref to CL_CRM_BOL_ENTITY_COL_UT. 
lv_result2 ?= CL_CRM_BOL_ENTITY_COL_UT=>Convert( lv_result ).

Above statement constructs the required Business Collection with uniform type. To fetch a data result with a BPATH query from it, the following syntax has to be used:

Data lv_bpath_result type ref to data.
Data lv_sourcecode type string.
lv_sourcecode = 'SearchResFlightRel/FlightBookRel/*$'.
lv_bpath_result = lv_result2->GET_PROPERTIES_BY_BPATH(
  IV_BPATH_STATEMENT = lv_sourcecode ).

If you want to execute the BPath query on a single object, the syntax is similar:

DATA: lv_element type ref to CL_CRM_BOL_ENTITY.
lv_element = lv_result->GET_LAST( ).
lv_bpath_result = lv_element->GET_PROPERTIES_BY_BPATH(
  IV_BPATH_STATEMENT = lv_sourcecode  ).

Before we get back to some more examples, some words on the Interpreter. BPath statements are directly translated into action (so it’s no compiled language). At the very beginning, it was implemented as top-down parser using recursive descent.  But after the first enhancements, it turned out that this approach is hardly maintainable and not extendable. So the parser was migrated to a stack machine.

The parser is implemented in the CL_WCF_BPATH_PARSER class. At first the scanner (method SCAN_NEXT) comes into picture which scans the input token by token. The scanner works context free, in means the result only depends on the currently processed characters and nothing else.The parser itself works in two passes. The first pass parses only the syntax without knowing anything about the semantics (it even does not know whether a specified relation or attribute name is valid), the second pass does the action. The parser uses two control tables which are built up at the beginning. The first holds all identifiers, identifiers are a level above tokens, not completely matching. The second table holds the possible status transistions controlling the parsing. In theory all parsing is done table controlled and only the action is done “coded”. But I guess in practise things are not that clean, but I haven’t checked now.

The parsing includes a lot of going forward and backward as for each entity the corresponding BPath fragment has to be evaluated again. To avoid too much operations for scanning, the first pass stores the intermediate results in a table also. There are mechanisms to avoid the assembly of the internal tables in case the data is already available.

As the recursive information is no more hold implecitely within the calling hierarchy, it has to be held explicitely. To do so a new stack class called CL_WCF_BPATH_STACK is used, implementing the stack with some pretty uncommon inventions. BPath actually uses quite a lot of stacks during processing.

The language itself does not differentiate between upper and lower case except where the underlying layer requires it (e.g. BOL is using case sensitive relation names). All elements must succeed directly after each other, it is not allowed to use blanks or carriage returns for separation.

~CRMS_BOL_UIF_TRAVEL_BOOK_KEY/SearchResFlightRel/FlightBookRel/*$
Code Example 7, ~STRUC, normal structures

In the examples of the last lesson the structure of the returned entity is set implicitly. It refers simply to the attribute structure of the corresponding object. Sometimes this is not required or wanted. So it is possible to set the structure explicitly as above.

The example uses the key structure which consists of 4 fields. The data is always filled with a move-corresponding statement (also when structures match). It is responsibility of the caller that the structures match, i.e. that the move-corresponding does not try to copy data to fields with the same name but of different type.
It is recommended to place the structure definition directly at the beginning, even though the syntax allows otherwise. With the first access to the structure, it has to be defined of course.

SearchResFlightRel/FlightBookRel[2]/*$
Code Example 8, [2] (basic filtering)

The [ ] clasps the filtering. The further evaluation is only done if the filtering can be done successfully. In our example we exclude all entries which are not the second entry within the FlightBookRel collection.
The filtering always relates to the most current relation. If we use the above source code on a collection the statement would return the second booking for every flight of the collection.
You may filter on every relation you specify. Syntactically it is correct to put several filters on one relation, eventough the result will be the same as if you use a corresponding conditional filter.

SearchResFlightRel/FlightBookRel[@CLASS=”C”]/*$
Code Example 9, [@CLASS=”C”] basic conditional filtering, string literals

There are two ways to filter, the first is operation on the index as in the previous chapter, the second is using boolean operations. If the statement is evaluated to true we proceed. Otherwise we check the next entry.
Please note, that string laterals are built using double quotes. It is possible to mask a double quote by stating two double quotes after each other.

Of course, the filtering is not restricted to simple comparisons with the equal operator. Let us check the next example:

SearchResFlightRel/FlightBookRel[(@CLASS<>”C”)&(@FORCURKEY=@LOCCURKEY)]/*$
Code Example 10, [(@CLASS<>”C”) &(@ FORCURKEY=@LOCCURKEY)], Comparisons and Bool-OPs

As you see it is also possible to use other comparison operators ( more exactly <, >, <=, >=, <>, = ). And it is possible to use the boolean functions and (with the & symbol) and or (using | ).

SearchResFlightRel/FlightBookRel[(@SMOKER+@INVOICE)=”X”]/*$
Code Example 11, [(@SMOKER+@INVOICE)=”X”], basic arithmetic operations

This example uses arithmetic operators, in this case as string concatenation operator. Both fields used are either “X” or empty so the expression basically evaluates to true if exactly one of the flags is set.

Please note that all operations are only working with 2 operands. An ‘or’ with three operands has to be expressed using appropriate brackets.

Beside above noted boolean and comparison operators the standard operators + (addition), – (substraction), * (multiplication) and % (division) are supported. The full operation table will be provided in the next lesson.

In general BPath operates with 4 data types: Strings, Dates, Numerics and Booleans. Within an intermediate value of an expression any of the above types may be used, but on the highest level of a filter it has to be either a boolean or a numeric value. For a numeric value an implicit check against the index is done. Note that negative value may be used also, as -1 represents the last entity of the collection (-2 consequently the second last, and so on).

With the next lesson we do something completely different. Assignments.

To report this post you need to login first.

17 Comments

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

    1. Juergen Gatter Post author
      Hi Luis
      Performance is always an important topic, so thank you for your interest and the question.

      Well, main directions of BPath are to be generic and to be of easy usage. As a generic implementation based on ABAP can hardly be faster then a well-done implementation in ABAP, goal is that the performance is not worse than a naive implementation plus a certain overhead. We did some investigations a year ago and according to these the overhead caused by BPath can be expected to be around 10% (but of course depending on the statement complexity), that’s a pretty good result I would say.

      Now is the question whether the naive implementation is always the best one.

      Actually I have to add that the filters above already contains a performance fix, which I didn’t state in the text above. Originally a filter was implemented as a check against the current index, leading to the effect, that all entries of the collection had to be checked harming performance drastically with big collection sizes. This was changed and a Rel[17] really means that only the 17.th element is processed. This works for all static filters returning a number (these are basically all, except those which need information from the object itself for calculation).

      There is another performance improvement which is actually not there. When you have a two 1:n relation hierachy rel1/rel2 the second relation is always read for every intermediate object. Reading all at once can be faster (depending on the application implementation), but only in case all entities are processed. As it can not be decided which way is better, an heuristic would have be an idea, but this is a future project.

      In general I would conclude that performance is quite fine as for the targeted adoptions the mentioned drawbacks are hardly important.

      Best regards, Juergen

      (0) 
      1. Luís Pérez Grau
        Hi Juergen,

        Thanks for the answer, I think BPath is pretty cool, as long the development stays in the “keep it simple” philosophy 🙂 I hope I can use it soon.

        Regards,

        Luis

        (0) 
    1. Juergen Gatter Post author
      Hi Raghu,

      Thanks for your feedback. The first five lessons are already out, I suppose there will be four more to come on this release over the next weeks.

      The next release will be interesting, because then it becomes … complex 😉 But there’s a little time till it is released.

      Regards, Juergen

      (0) 
  1. Pallavi A.N

    Hello Jurgen,

    Thanks for the blog.

    I tried BPath with DQuery ‘BuilHeaderAdvancedSearch’.

    I want to retrieve key structure of address relation. So I used below source code.

    ‘~CRMT_OST_BUPA_ADDRESS_KEY/BuilAddressRel/*$’


    But it returns result with all zeros in address key. Previously I tried/BuilAddressRel/*$’ . It worked fine and retrieved 10 address details. Could you please correct me if I am using the wrong source code?


    Thanks,

    Pallavi

    (0) 
    1. Juergen Gatter Post author

      Hi Pallavi,

      as far as I can see, the relation returns CRMT_BUPA_IL_ADDRESS structure, in which there is a field ADDRESS_GUID which is char32 whereas CRMT_OST_BUPA_ADDRESS_KEY has a field ADDRESS_KEY which is raw16.

      I am a bit unsure whether BPATH is able to do the conversion from char32 to raw16, but as the * operator is doing a move-corresponding operation, it can not match ADDRESS_KEY to ADDRESS_GUID as there are different names used. You might try a
      {!ADDRESS_KEY=@ADDRESS_GUID} assignment block (see next chapter for syntax).

      Hmm, I just realized that some of the examples in lesson 3 seems to be broken. I will check this.

      Best regards, Juergen

      (0) 
  2. Santosh Kotra

    Hello Juergen,

    I was trying to use the filtering option with a variable lv_filter as below.

    lv_filter = ‘MAT1’.

    get_related_entities_by_bpath( ‘BTOrderHeader/BTHeaderItemsExt/BTOrderItemAll[@ORDERED_PROD=”{ lv_filter }”]/*$’ ).

    The above code doesn’t return any results but when I hardcode it as below, it works.

    get_related_entities_by_bpath( ‘BTOrderHeader/BTHeaderItemsExt/BTOrderItemAll[@ORDERED_PROD=”MAT1″]/*$’ ).


    I would like to filter dynamically based on the input.


    Please could you check if I am doing it wrong. Thank you.


    Best Regards,

    Santosh

    (0) 
    1. Juergen Gatter Post author

      Hello Santosh,

      actually BPath is only working hardcoded, there is no automatic replacement of variables implemented. In case you want to use BPath based on user input you have to do the string replacements on your own.

      Ok?

      Best Regards, Juergen

      (0) 
  3. Santosh Kotra

    Hello Juergen,


    This is me again, I wanted to bypass buffer using get_related_entites_by_bpath. Is that possible, I don’t see any parameters for that.


    Please help.


    Regards,

    Santosh

    (0) 
    1. Juergen Gatter Post author

      Hello Santosh,

      as far as I remember we did never work on the possibility to bypass the buffer on working with BPath. So unfortunately I guess it is not possible.

      Best Regards, Juergen

      (0) 
  4. Marat Akhmetzyanov

    Hello Juergen,

    If I want to get value STREET from BP ADDRESS entity with type ZZSTORE, how can I do this with use of BPATH?

     dref = lr_partner->get_properties_by_bpath(
              'BuilAddressRel/@STREET/BuilAddressUsageRel[@ADDRESSTYPE="ZZSTORE"]' ).

    This BPATH doesn’t work 🙁

    (0) 
    1. Marat Akhmetzyanov

      At the end I am stoped at such variant:

            dref = lr_partner->get_related_entities_by_bpath(
                'BuilAddressRel/BuilAddressUsageRel[@ADDRESSTYPE="ZZPOST"]/*'
                )->get_first( )->get_parent( )->get_property( 'STREET' ).

      but it’s not clear BPATH.

      (0) 

Leave a Reply