A class for dynamic queries
Forewords
I recently wrote a Yet another webdynpro (ABAP) se16 clone
on a webdynpro SE16-like data browser based on a custom class. Now it’s
time to explain how that class works. This has nothing to do
with webdynpro, only dynamic ABAP programming.The source code for this
stuff is available on my google code page.
Class architecture<br />This
class in named
zcl_se16_core
and relys heavily on cl_abap_typedescr
and its subclasses.Its main goal is to implement the programming logic
of se16, leaving to others both the user interface and error handling
(it does a little exception handling, but for the most part only lets
exceptions flow to the caller).
Every instance deals with a specific table (or view) specified in the constructor.
Attributes
public:
TABNAME – the name of the table currently processed
TABDESC – table description in current language
FIELDS – all the table fields
MAINFIELDS – key fields + fields with a control table
USERFIELDS – all the table fields but the client
private:
DESCR – CL_ABAP_STRUCTDESCR object for the current table
OPTIONS – select options used to build the dynamic query
TAB_DATA – table contents
Constructor<br />
First of all I take the table name and get its typedescr. I use a
temporary variable because of type mismatch, and then cast it to global
parameter descr. As in most of this code I leave to the caller the
burden of handling exceptions such as casting errors.In this case I
went a little too far: I should really check that the given structure
is actually a table or a view calling GET_DDIC_HEADER, but as long as I
keep handling exceptions trying to select from a structure is not
really dangerous, only annoying.
The rest of the method simply fills various public attributes: field lists and table details.
method constructor.
data: field like line of fields,
desctmp type ref to cl_abap_typedescr.
call method cl_abap_structdescr=>describe_by_name
exporting
p_name = name
receiving
p_descr_ref = desctmp
exceptions
type_not_found = 1.
if sy-subrc <> 0.
raise exception type cx_sy_create_object_error.
endif.
descr ?= desctmp.
tabname = name.
fields = descr->get_ddic_field_list( ).
userfields = fields.
delete userfields where fieldname eq ‘MANDT’.
loop at userfields into field where keyflag is not initial or checktable is not initial.
append field to mainfields.
endloop.
select single ddtext from dd02t into tabdesc
where tabname = tabname
and ddlanguage = sy-langu
and as4local = ‘A’.
if sy-subrc ne 0.
select single ddtext from dd02t into tabdesc
where tabname = tabname
and ddlanguage = ‘E’
and as4local = ‘A’.
endif.
endmethod.
Adding a select-option<br />I store the select
options in an internal table with a two columns: a fieldname and a
reference to a range table.Method add_sel gets two matching parameters:
if the range table is not empty fills an entry in the options table
after copying the range table to a newly created unnamed
variable.Copying the range slows the program and eats memory but may
save trouble with unbound references.To tell the truth I think this
copying code is pretty useless, but when I coded it I was in a paranoid
mood.<br />Empty range is useless for my purposes, so I simply ignore them. <br /><div style=”margin-left: 40px”><br />method add_sel.<br />
data: opt type selopt_el.<br />
field-symbols:<intab> type table,<outtab> type table.<br />
try.<br />
assign rangetab->* to <intab>.<br />
check <intab> is not initial.<br />
create data opt-rangetab like <intab>.<br />
assign opt-rangetab->* to <outtab>.<br />
<outtab> = <intab>.<br />
opt-id = id.<br />
append opt to options.<br />
catch cx_root.<br />
endtry.<br />
endmethod.<br /><br /></div>the method for clearing the options table is too trivial to be discussed:<br /><div style=”margin-left: 40px”><br />
method CLEAR_SEL.<br />
free OPTIONS.<br />
endmethod.
<br /><br /></div>Creating a dynamic variable with all the needed ranges<br />Method
create_data returns a condition string and a reference to a structure
which contains all the needed ranges.This is the trickiest part of the
code.<br />The ABAP syntax for dynamyc where conditions require a
distinct name for every range table used.Usually I use a different
variable for every range, but in this case I’ll use a single complex
variable with several ranges inside, such as:<br /><br /><div style=”margin-left: 40px”>data:begin of range_str,<br /> rcarrid type range of carrid,<br /> rflid type range of flid,<br /> end of rangetab,<br /> wherecond type string.<br />wherecond = ‘carrid in range_str-rcarrid and flid in range_str-rflid’.<br /></div><br />Since
i need an unknown number of ranges and I don’t know their type in
advance, I’ll create the range structure dynamically, calling the
fields with a simple prefix followed by a number.<br />To create the
dynamic structure I first fill a component table of
type cl_abap_structdescr=>component_table with a record for every
line of the options table, setting the field type from the range it
references and the field id and generating the name with a counter.<br />Then I create a structdesc object and use it to create the dynamic range structure:<br /><div style=”margin-left: 40px”>struct_type = cl_abap_structdescr=>create( comp_tab ).<br />create data rangestruct type handle struct_type.<br /></div>Finally
I loop on options again to copy the range into the structure, and at
the same time I create the condition string. This needs a lot of field
symbols since I’m working with dynamic objects, but aside from that
it’s pretty straightforward.Of course the condition string only works
with the ranges structure assigned to a field symbol named <sel>.<br />If the options table is empty, the condition string will be emty too and the range structure reference will be unbound.<br /><br /><div style=”margin-left: 40px”>
method create_data.<br />
field-symbols:<sel> type any,<br />
<optt> type any table,<br />
<sel_f> type any table.<br />
data: struct_type type ref to cl_abap_structdescr,<br />
comp_tab type cl_abap_structdescr=>component_table,<br />
comp like line of comp_tab.<br />
data: field_id(4) type n,<br />
opt type selopt_el,<br />
fieldname type string.<br />
loop at options into opt.<br />
concatenate ‘S_’ field_id into comp-name.<br />
comp-type ?= cl_abap_elemdescr=>describe_by_data_ref( opt-rangetab ).<br />
append comp to comp_tab.<br />
field_id = field_id + 1.<br />
endloop.<br />
if comp_tab is not initial.<br />
struct_type = cl_abap_structdescr=>create( comp_tab ).<br />
create data rangestruct type handle struct_type.<br />
assign rangestruct->* to <sel>.<br />
loop at options into opt.<br />
assign opt-rangetab->* to <optt>.<br />
read table comp_tab into comp index sy-tabix.<br />
concatenate ‘<sel>-‘ comp-name into fieldname.<br />
if sy-tabix = 1.<br />
concatenate opt-id ‘in’ fieldname into condition separated by space.<br />
else.<br />
concatenate condition ‘and’ opt-id ‘in’ fieldname into condition separated by space.<br />
endif.<br />
assign COMPONENT comp-name OF STRUCTURE <sel> to <sel_f>.<br />
<sel_f> = <optt>.<br />
endloop.<br />
endif.<br />
endmethod.
<br /></div>
<br />There’s a limit on the number of fields in the range structure, but
as someone pointed the same holds for the table we’re trying to read
from the database, so it won’t be an issue.<br /><br />Query execution<br />This
is pretty straightforward: first it creates an unnamed table storing
its reference in tab_data, leveraging on the fact that we’re dealing
with DDIC objects, then assigns a couple of field symbols and runs the
query.If the condition string is empty it omits the where condition
completely.<br />That’s all.
I used the same class for a classic report too, maybe I’ll write about it in another post.