Sometimes you may need to directly access data from a table without any function module or webservice. SAP offers a dedicated function module for direct database access via RFC: RFC_READ_TABLE
. But RFC is unfortunately not available everywhere, so we decided to create a dedicated restful service to provide a similar function as RFC_READ_TABLE
.
SICF handler
Our SICF service handler is basically the same as in the JSON Adapter for ABAP Function Modules, but we removed the call to the function module and replaced it with an authority check on table leven (VIEW_AUTHORITY_CHECK
) and the call to our service method:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
METHOD if_http_extension~handle_request.
...
****************************
* Get form and header fields
****************************
" Get table name from PATH_INFO
path_info = server->request->get_header_field( name = '~path_info' ).
SPLIT path_info AT '/' INTO path_info lv_tabname.
TRANSLATE lv_tabname TO UPPER CASE.
IF lv_tabname IS INITIAL.
http_error '404' 'Not Found' 'Empty request.' .
ENDIF.
lv_format = server->request->get_form_field( 'format' ).
...
****************************
* Check Authorization
****************************
CALL FUNCTION 'VIEW_AUTHORITY_CHECK'
EXPORTING
view_action = 'S'
view_name = CONV viewname( lv_tabname )
EXCEPTIONS
table_not_found = 3
OTHERS = 99.
CASE sy-subrc.
WHEN 0.
" all good :)
WHEN 3.
" table not found
http_error '404' 'Not found' 'The table you have requested has not been found'.
WHEN OTHERS.
" insufficient permissions
http_error '403' 'Not allowed' 'You are not allowed to query this table'.
ENDCASE.
****************************
* get input data *
****************************
DATA(lv_columns) = server->request->get_form_field( 'columns' ).
DATA(lv_query) = server->request->get_form_field( 'query' ).
DATA(lv_rowcount) = server->request->get_form_field( 'rowcount' ).
DATA(lv_orderby) = server->request->get_form_field( 'orderby' ).
****************************
* read the table
****************************
TRY.
DATA(lr_data) = read_table(
iv_tabname = lv_tabname
iv_columns = lv_columns
iv_query = lv_query
iv_rowcount = lv_rowcount
iv_orderby = lv_orderby ).
CATCH cx_root INTO oexcp.
etext = oexcp->if_message~get_longtext(
preserve_newlines = abap_true ).
IF etext IS INITIAL.
etext = oexcp->get_text( ).
ENDIF.
http_error '500' 'Internal Server Error' etext.
ENDTRY.
* Prepare response. Serialize to output format stream.
CASE lv_format.
WHEN 'XML'.
serialize_xml(
EXPORTING
data = lr_data
lowercase = lv_lowercase
format = lv_format
IMPORTING
o_string = o_cdata ).
server->response->set_header_field(
name = 'Content-Type'
value = 'application/xml' ).
WHEN OTHERS. " the others default to JSON.
lv_format = 'JSON'.
serialize_json(
EXPORTING
tabname = lv_tabname
data = lr_data
lowercase = lv_lowercase
camelcase = lv_camelcase
IMPORTING
o_string = o_cdata ).
server->response->set_header_field(
name = 'Content-Type'
value = 'application/json' ).
ENDCASE.
...
ENDMETHOD.
Dynamically create the SQL statement
In our service method we dynamically generate the SQL statement according to the get parameters of the HTTP call:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
METHOD read_table.
DATA: lv_columns TYPE string,
lv_rowcount TYPE i.
DATA: lt_components TYPE cl_abap_structdescr=>component_table.
DATA: lr_data TYPE REF TO data.
FIELD-SYMBOLS: <t_data> TYPE STANDARD TABLE.
lv_columns = iv_columns.
TRY.
lv_rowcount = iv_rowcount.
CATCH cx_root.
ENDTRY.
TRY.
" Wanted columnns
IF lv_columns IS NOT INITIAL.
TRANSLATE lv_columns TO UPPER CASE.
" support for , AND ; as column separator
REPLACE ALL OCCURRENCES OF ';' IN lv_columns WITH ','.
SPLIT lv_columns AT ',' INTO TABLE DATA(lt_columns).
LOOP AT lt_columns ASSIGNING FIELD-SYMBOL(<s_column>).
CONDENSE <s_column>.
ENDLOOP.
ENDIF.
CLEAR lv_columns.
" Column names via RTTI
DATA(lo_struct_desc) = CAST cl_abap_structdescr(
cl_abap_structdescr=>describe_by_name( iv_tabname ) ).
IF lt_columns IS INITIAL.
" take the columns in the order of the table
lv_columns = '*'.
lt_components = CORRESPONDING #( lo_struct_desc->get_included_view( ) ).
ELSE.
" take the columns in the order of the column list
LOOP AT lt_columns ASSIGNING <s_column>.
READ TABLE lo_struct_desc->get_included_view( )
INTO DATA(ls_component) WITH KEY name = <s_column>.
IF sy-subrc EQ 0.
IF lv_columns IS NOT INITIAL.
lv_columns = lv_columns && `, `.
ENDIF.
lv_columns = lv_columns && ls_component-name.
APPEND CORRESPONDING #( ls_component )
TO lt_components.
ENDIF.
ENDLOOP.
ENDIF.
" create the outtab dynamically
lo_struct_desc = cl_abap_structdescr=>create( lt_components ).
DATA(lo_table_desc) = cl_abap_tabledescr=>create( lo_struct_desc ).
CREATE DATA lr_data TYPE HANDLE lo_table_desc.
ASSIGN lr_data->* TO <t_data>.
SELECT (lv_columns)
FROM (iv_tabname)
WHERE (iv_query)
ORDER BY (iv_orderby)
INTO CORRESPONDING FIELDS OF TABLE @<t_data>
UP TO @lv_rowcount ROWS.
IF sy-subrc EQ 0.
rr_data = lr_data.
ENDIF.
CATCH cx_root INTO DATA(lo_ex).
RAISE EXCEPTION TYPE zcx_json
EXPORTING
message = lo_ex->if_message~get_text( ).
ENDTRY.
ENDMETHOD.
Call the restful service
Using the code above, we can call the restful service to get all wanted table entries from the chosen table: https://<server>:<port>/read_table/<tablename>?sap-client=<client>&format=<format>&query=<query>&rowcount=<rowcount>&columns=<columns>&orderby=<orderby>
where the values can be inserted like:
<server>
/<port>
: your server name and port<tablename>
: the table name (the calling user needs access to this table - S_TABU_NAM/S_TABU_DIS)<format>
: Either XML or JSON<query>
: The SQL query (url encoded). An empty query returns all data<rowcount>
: The amount of rows to be returned<columns>
: the columns you want in the response (Empty = SELECT *)<orderby>
: ASCENDING, DESCENDING or empty