The problem
In the documentation of CP we see:
You see that bit in red? That means
'abc' CP 'Ab*'.
Good to know.
So, what about in select-options (and ranges)? Perhaps we've a bunch of RFC destinations in table RFCDES, and we've got destinations
MATT,
matt, and
Matt. Slightly strange RFC destinations - one wonders about the narcissism of the definer!
So, I've got a select-option
DATA g_rfcdest TYPE rfcdest.
SELECT-OPTIONS so_rfc FOR g_rfcdest.
In my select option, I have the single entry
Ma*. What happens with a select from the database?
SELECT rfcdest INTO TABLE @data(destinations) WHERE rfcdest IN @so_rfc.
Great - it works. I end up with a table, containing only
Matt. Let's try the same with an internal table.
SELECT rfcdest INTO TABLE @DATA(destinations) FROM rfcdes.
DELETE destinations WHERE rfcdest NOT IN so_rfc.
Oh. It doesn't work. It turns out that with select options that have patterns, if they're used with internal tables, the match is case-insensitive.
So what now? Is it so insensitive that it reduces us to tears?
A solution
To a man with hammer, everything is a nail. I'm an ABAPper, so naturally, I'm going for a programmatic solution.
Well, the documentation with CP says that we should use # as an escape character. If my range is 10 characters, and I only want
Ma* then I supply
#M#a*. OK. But now I want to see if there are matches to
Matthe* - so I supply
#M#a#t#t#h#e*. Oops - that 13 characters. Too long for the range.
What might not be widely appreciated is, that with a range or select option, it doesn't matter too much what the actual type used is for the range. So even if my internal table field is ten characters, I could, for example, use a range of strings, and see if my entry is
IN that.
So that's what I did. A couple of small utility methods to do the conversion. And in 7.4 and above, you can make use of some of the new language features to make it very tidy indeed.
REPORT zcs_range.
CLASS lcl_case_tools DEFINITION.
PUBLIC SECTION.
TYPES ty_string_range TYPE RANGE OF string.
METHODS get_string_range
IMPORTING
i_range TYPE STANDARD TABLE
RETURNING
VALUE(r_string_range) TYPE ty_string_range.
METHODS make_case_sensitive
IMPORTING
i_value TYPE clike
RETURNING
VALUE(r_cs_value) TYPE string.
PRIVATE SECTION.
METHODS is_letter
IMPORTING
i_letter TYPE string
RETURNING
VALUE(r_is_letter) TYPE abap_bool.
ENDCLASS.
CLASS lcl_case_tools IMPLEMENTATION.
METHOD get_string_range.
r_string_range = VALUE #( FOR wa IN CORRESPONDING ty_string_range( i_range )
( sign = wa-sign
option = wa-option
low = me->make_case_sensitive( wa-low )
high = me->make_case_sensitive( wa-high ) ) ).
ENDMETHOD.
METHOD make_case_sensitive.
IF i_value IS INITIAL.
RETURN.
ENDIF.
DATA(pos) = 0.
DO.
IF pos EQ strlen( i_value ).
EXIT.
ENDIF.
DATA(single_char) = substring( val = i_value off = pos len = 1 ).
r_cs_value = r_cs_value && COND #( WHEN me->is_letter( single_char ) THEN '#' ) && single_char..
ADD 1 TO pos.
ENDDO.
ENDMETHOD.
METHOD is_letter.
r_is_letter = xsdbool( to_upper( i_letter ) CA sy-abcde ).
ENDMETHOD.
ENDCLASS.
START-OF-SELECTION.
TYPES ty_stuff TYPE STANDARD TABLE OF char3 WITH NON-UNIQUE KEY table_line.
DATA(stuff_to_handle) = VALUE ty_stuff( ( 'abc' ) ( 'ABC' ) ( 'aBC' ) ).
cl_demo_output=>begin_section( 'The stuff' ).
cl_demo_output=>write_data( stuff_to_handle ).
DATA stuff_range TYPE RANGE OF char3.
stuff_range = VALUE #( ( sign = 'I' option = 'CP' low = 'A*' ) ).
cl_demo_output=>next_section( 'The range' ).
cl_demo_output=>write_data( stuff_range ).
DATA(ncs_stuff) = stuff_to_handle.
DELETE ncs_stuff WHERE table_line IN stuff_range.
cl_demo_output=>next_section( 'Non-case sensitive removal' ).
cl_demo_output=>write_data( ncs_stuff ).
DATA(cs_stuff) = stuff_to_handle.
DELETE cs_stuff WHERE table_line IN NEW lcl_case_tools( )->get_string_range( stuff_range ).
cl_demo_output=>next_section( 'Case sensitive removal' ).
cl_demo_output=>write_data( cs_stuff ).
cl_demo_output=>end_section( ).
DATA(pattern) = 'Ab*'.
cl_demo_output=>begin_section( 'The pattern' ).
cl_demo_output=>write_data( pattern ).
cl_demo_output=>next_section( '''abc'' CP ''Ab*''' ).
IF 'abc' CP pattern.
cl_demo_output=>write_text( 'It covers it' ).
ELSE.
cl_demo_output=>write_text( 'It does not cover it' ).
ENDIF.
cl_demo_output=>next_section( 'Case sensitive ''abc'' CP ''Ab*''' ).
IF 'abc' CP NEW lcl_case_tools( )->make_case_sensitive( pattern ).
cl_demo_output=>write_text( 'It covers it' ).
ELSE.
cl_demo_output=>write_text( 'It does not cover it' ).
ENDIF.
cl_demo_output=>display( ).
I do have a couple of questions.
- Does anyone know where the case-insensitivity of select-options/ranges with respect to internal tables is documented. I couldn't find it.
- How do select-options/ranges work with selects from internal tables? I'm assuming they'll be case sensitive - but unfortunately, I don't have such a system to play with.