Catch SAPSQL_DATA_LOSS - exception

I want to catch and handle SAPSQL_DATA_LOSS in my ABAP code.
I tried this:
try.
SELECT *
FROM (rtab_name) AS rtab
WHERE (sub_condition)
into table #<sub_result>
.
catch SAPSQL_DATA_LOSS into error.
...
endtry.
But above code is not valid. I get this message:
Type "SAPSQL_DATA_LOSS" is not valid
And I tried this:
catch SYSTEM-EXCEPTIONS SAPSQL_DATA_LOSS = 123.
SELECT *
...
.
endcatch.
if sy-subrc = 123.
...
endif.
But above code gives me:
Instead of "SAPSQL_DATA_LOSS" expected "system-exception" (translated from german to english by me)
How to catch SAPSQL_DATA_LOSS?
This question is not about "why does this exception happen?". This is already solved. My code should handle the exception.

SAPSQL_DATA_LOSS is a runtime error.
As SAPSQL_DATA_LOSS is not a class-based exception, it is not possible to catch it using try catch.
As SAPSQL_DATA_LOSS is not a catchable runtime error, it is not possible to catch it using try catch SYSTEM-EXCEPTIONS.
see the below catchable runtime errors.
https://help.sap.com/doc/abapdocu_751_index_htm/7.51/en-US/abenueb-abfb-sysexc.htm

After some tries I can propose you a possible solution.
This is a workaround:
I don't know if it can be applied to your case, since it needs the select statement to be wrapped into an RFC function module !
The main point is that a short dump (message type X) CAN be handled in RFC calls.
So using an RFC (CALL FUNCTION 'xxxxx' destination 'NONE' for example) and using special exception SYSTEM_FAILURE, the system does not terminate the caller program, but instead it returns a SY-SUBRC > 0 with the Short dump informations in system message fields (SY-MSGxx).
STEPS
Create a Function module (RFC enabled) with your select statement input + the row type of the result table. (All parameters passed by value)
You need this last parameter since generic tables can't be passed in RFC (no "TYPE ANY TABLE" allowed)
FUNCTION Z_DYN_SEL .
*"----------------------------------------------------------------------
*"*"Local interface:
*" IMPORTING
*" VALUE(RTAB_NAME) TYPE TABNAME16
*" VALUE(SUB_CONDITION) TYPE STRING
*" VALUE(RESULT_TYPE) TYPE STRING
*"----------------------------------------------------------------------
* RTAB_NAME: DB Table
* SUB_CONDITION: WHERE Condition
* RESULT_TYPE: The ROW type of the internal table
field-symbols <sub_result> type any table.
* DEFINE LOCAL DYNAMIC TABLE TO STORE THE RESULT
data: lr_res type ref to data.
create data lr_res type standard table of (result_type).
assign lr_res->* to <sub_result>.
* DYNAMIC SELECT
select *
from (rtab_name) as rtab
where (sub_condition)
into table #<sub_result>.
* EXPORT RESULT TO A MEMORY ID, SO IT CAN BE RETRIEVED BY CALLER
export res = <sub_result> to memory id 'RES'.
Main program:
In this caller example some parameters are passed to the RFC.
KTOKD field (should be 4 chars long) is passed with a char10 value (producing your short dump).
If ANY Dump is triggered inside the function, we can now handle it.
If everything went fine, IMPORT result from the EXPORT statement inside the RFC
field-symbols <sub_result> type any table.
data: lr_res type ref to data.
create data lr_res type standard table of KNA1.
assign lr_res->* to <sub_result>.
data lv_msg type char255.
call function 'Z_DYN_SEL' destination 'NONE'
exporting
rtab_name = 'KNA1'
sub_condition = `KTOKD = 'D001xxxxxx'`
result_type = 'KNA1'
exceptions
system_failure = 1 message lv_msg.
if sy-subrc = 0.
import res = <sub_result> from memory id 'RES'.
else.
write: / lv_msg.
write : / sy-msgid, sy-msgno, sy-msgty, sy-msgv1, sy-msgv2, sy-msgv3, sy-msgv4.
endif.
RESULTS
After the RFC call in case of a short dump in the select statement, the program is not terminated and the following pieces of information are available
SY-SUBRC = 1
lv_msg is the error text (Data was lost while copying a value.)
Sy-msgid = 00
Sy-msgno = '341'
Sy-msgty = 'X'
Sy-msgv1 = 'SAPSQL_DATA_LOSS'

Related

Can I declare type of parameters for sql command? I cannot insert a boolean value, it is being considered a string

I am trying to insert into MySQL DB using Powershell, the input data is from a REST API call. I am using Prepare Statement approach to optimize the inserts, I am having issues while inserting values into a column (let my_col_bool ) which is of type Boolean (i.e tinyint(1)).
The input data received from REST API would assign values to $myVar1,$myVar3,$myVar3. The values assigned to $myVar3 would be "true / false", as I am adding these values to command parameter and Executing the query, may be it is considering these values as String instead of Boolean as I am having an Error.
Approach 1:
$oMYSQLCommand.CommandText = "INSERT INTO myTable VALUES(#my_col_string,#my_col_int,#my_col_bool)"
cmd.Prepare()
$oMYSQLCommand.Parameters.AddWithValue("#my_col_string", "")
$oMYSQLCommand.Parameters.AddWithValue("#my_col_int", "")
$oMYSQLCommand.Parameters.AddWithValue("#my_col_bool", "")
$oMYSQLCommand.Parameters("#my_col_string").Value = $myVar1
$oMYSQLCommand.Parameters("#my_col_int").Value = $myVar2
$oMYSQLCommand.Parameters("#my_col_bool").Value = $myVar3
$oMYSQLCommand.ExecuteNonQuery() /*Error: Exception calling "ExecuteNonQuery" with "0" argument(s): "Incorrect integer value: 'false' for column 'my_col_bool' at row 1" */
Approach 2:
$oMYSQLCommand.Parameters.Add("#my_col_bool",[System.Data]::$SqlDbType.TinyInt) /*Error: Unable to find type [System.Data] */
Approach 3:
$oMYSQLCommand.Parameters.Add("#my_col_bool",$SqlDbType.TinyInt) /*Error: Cannot find an overload for "Add" and the argument count: "2". */
Approach 4:
$param_var = New-Object MySql.Data.MySqlClient.MySqlParameter("#my_col_bool",$SqlDbType.TinyInt)
$oMYSQLCommand.Parameters.Add($param_var) | Out-Null
$oMYSQLCommand.ExecuteNonQuery()/*Error: Exception calling "ExecuteNonQuery" with "0" argument(s): "Incorrect integer value: 'false' for column 'my_col_bool' at row 1" */
Every .Net driver tries to have exact same interface as MS SQL Connector has.
MS SQL Example:
$sqlCmd = [System.Data.SqlClient.SqlCommand]::new()
[void]$sqlCmd.Parameters.AddWithValue('#param1', [System.Data.SqlTypes.SqlInt16]::new(22))
[void]$sqlCmd.Parameters.Add('#param2', [System.Data.SqlTypes.SqlInt16])
$sqlCmd.Parameters['#param2'].Value = [System.Data.SqlTypes.SqlInt16]::new(22)
Reference: System.Data.SqlTypes
So usually, the methods are same, you just have to use different namespace inside [].
Note that some .Net providers use SQL type system, and some use own type system, which is usually at [VendorName.something] namespace.
For example, MySQL seems to use [MySql.Data.MySqlClient.MySqlDbType]::%typeName% namespace,
Here is update on my approach 3, to make it work $myVar1 should be bool variable
$oMYSQLCommand.Parameters.Add("#my_col_bool",$SqlDbType.TinyInt)
[bool]$myVar3_bool=[boolean]::parse($myVar1)
$oMYSQLCommand.Parameters("#my_col_bool").Value = $myVar3_bool

Insert data into JSON column in postgres using JOOQ

I have a postgres database to which I read/write using JOOQ. One of my DB tables has a column of type JSON. When I try to insert data into this column using the query below, I get the error
Exception in thread "main" org.jooq.exception.DataAccessException: SQL [update "public"."asset_state" set "sites_as_json" = ?]; ERROR: column "sites_as_json" is of type json but expression is of type character varying
Hint: You will need to rewrite or cast the expression.
Below is the code for inserting data into the column
SiteObj s1 = new SiteObj();
s1.setId("1");
s1.setName("Site1");
s1.setGeofenceType("Customer Site");
SiteObj s2 = new SiteObj();
s2.setId("2");
s2.setName("Site2");
s2.setGeofenceType("Customer Site");
List<SiteObj> sitesList = Arrays.asList(s1, s2);
int result = this.dsl.update(as).set(as.SITES_AS_JSON, LambdaUtil.convertJsonToStr(sitesList)).execute();
The call LambdaUtil.convertJsonToStr(sitesList) outputs a string that looks like this...
[{"id":"1","name":"Site1","geofenceType":"Customer Site"},{"id":"2","name":"Site2","geofenceType":"Customer Site"}]
What do I need to do to be able to insert into the JSON column?
Current jOOQ versions
jOOQ natively supports JSON and JSONB data types. You shouldn't need to have to do anything custom.
Historic answer
For jOOQ to correctly bind your JSON string to the JDBC driver, you will need to implement a data type binding as documented here:
https://www.jooq.org/doc/latest/manual/code-generation/custom-data-type-bindings
The important bit is the fact that your generated SQL needs to produce an explicit type cast, for example:
#Override
public void sql(BindingSQLContext<JsonElement> ctx) throws SQLException {
// Depending on how you generate your SQL, you may need to explicitly distinguish
// between jOOQ generating bind variables or inlined literals.
if (ctx.render().paramType() == ParamType.INLINED)
ctx.render().visit(DSL.inline(ctx.convert(converter()).value())).sql("::json");
else
ctx.render().sql("?::json");
}

Capture any standard report to JSON or XML?

I know that I can use LIST_TO_ASCI to convert a report to ASCII, but I would like to have a more high level data format like JSON, XML, CSV.
Is there a way to get something that is easier to handle then ASCII?
Here is the report I'd like to convert:
The conversion needs to be executed in ABAP on a result which was executed like this:
SUBMIT <REPORT_NAME> ... EXPORTING LIST TO MEMORY AND RETURN.
You can get access to SUBMIT list in memory like this:
call function 'LIST_FROM_MEMORY'
TABLES
listobject = t_list
EXCEPTIONS
not_found = 1
others = 2.
if sy-subrc <> 0.
message 'Unable to get list from memory' type 'E'.
endif.
call function 'WRITE_LIST'
TABLES
listobject = t_list
EXCEPTIONS
EMPTY_LIST = 1
OTHERS = 2
.
if sy-subrc <> 0.
message 'Unable to write list' type 'E'.
endif.
And the final step of the solution (conversion of result table to JSON) was already answered to you in your question.
I found a solution here: http://zevolving.com/2015/07/salv-table-22-get-data-directly-after-submit/
This is the code:
DATA: lt_outtab TYPE STANDARD TABLE OF alv_t_t2.
FIELD-SYMBOLS: <lt_outtab> like lt_outtab.
DATA lo_data TYPE REF TO data.
" Let know the model
cl_salv_bs_runtime_info=>set(
EXPORTING
display = abap_false
metadata = abap_false
data = abap_true
).
SUBMIT salv_demo_table_simple
AND RETURN.
TRY.
" get data from SALV model
cl_salv_bs_runtime_info=>get_data_ref(
IMPORTING
r_data = lo_data
).
ASSIGN lo_data->* to <lt_outtab>.
BREAK-POINT.
CATCH cx_salv_bs_sc_runtime_info.
ENDTRY.
Big thanks to Sandra Rossi, she gave me the hint to cx_salv_bs_sc_runtime_info.
Related answer: https://stackoverflow.com/a/52834118/633961

Attempt to perform arithmetic on field 'x' (a table value)

I'm working with lua-alchemy, and I'm setting a global variable in my AS3 code in this way:
_lua.setGlobal("map", _map);
With _map being a object with the following function in it:
public function get x():int
{
return 10;
}
if then I try to do something like this in Lua
local a = map.x + 1
I get the following error:
Lua script failed: luaDoString:21: attempt to perform arithmetic on field 'x' (a table value)
Does anyone knows why it does that, and how I could fix it?
EDIT :
When I print type(map.id), it prints table... Shouldn't it print number?
I found my error. According to this page, I have to use as3.tolua(map.x) to convert it to the right type.

How to convert generic exception into BAPIRET2 message?

I have several custom exception classes that were created "With Message Class". Since I can't directly get a message from them, I want to create a utility method that returns a BAPIRET2 from a given exception based on the values in IF_T100_MESSAGE~T100KEY. However, I can't provide that method with a generic CX_ROOT importing parameter as this class is not message-enabled. I also can't create a generic message-enabled exception class as new classes have to inherit from one of CX_STATIC_CHECK, CX_DYNAMIC_CHECK, or CX_NOCHECK.
How can I then retrieve the message details from an unspecified exception? Should I create a method that receives a CX_ROOT and then does up to three calls to methods with an import typed to each of the three possible subclasses? Or are there better alternatives?
You could prepare a type descriptor of the interface (once):
DATA: lr_t100_descr TYPE REF TO cl_abap_intfdescr.
lr_t100_descr ?= cl_abap_typedescr=>describe_by_name( 'IF_T100_MESSAGE' ).
and then examine each exception as it comes your way:
DATA: lr_t100_exception TYPE REF TO if_t100_message.
IF lr_t100_descr->applies_to( ir_any_exception ) = abap_true.
lr_t100_exception ?= ir_any_exception.
" ...
ENDIF.
You could use the message collector object, so for example
DATA:
excp type ref to CX_ROOT,
bapi_messages type BAPIRETTAB,
message_collector type ref to IF_RECA_MESSAGE_LIST.
FIELD_SYMBOLS:
<bapi_message> TYPE BAPIRET2.
message_collector = cf_reca_message_list=>create( ).
TRY.
" some code which may cause and exception
CATCH cx_root into excp.
message_collector->add_from_exxeption( io_exception = excp).
ENDTRY.
bapi_messages = message_collector->get_list_as_bapiret( ).
LOOP AT bapi_messages ASSIGNING <bapi_message>.
" write out message
ENDLOOP.
It is well worth checking out the message collector object.
For example
http://wiki.scn.sap.com/wiki/display/profile/2007/07/09/Message+Handling+-+Finding+the+Needle+in+the+Haystack
For a logging class I use something like this:
METHOD add_message_exception.
DATA:
lr_type TYPE REF TO cl_abap_typedescr,
lr_class TYPE REF TO cl_abap_classdescr,
lr_intf TYPE REF TO cl_abap_intfdescr,
l_bapiret2 TYPE bapiret2,
lr_msg TYPE REF TO if_t100_message.
CHECK ir_exception IS NOT INITIAL.
l_bapiret2-type = i_type.
"Test for T100KEY interface
cl_abap_classdescr=>describe_by_object_ref(
EXPORTING
p_object_ref = ir_exception
RECEIVING
p_descr_ref = lr_type
EXCEPTIONS
reference_is_initial = 1
OTHERS = 2 ).
TRY.
lr_class ?= lr_type.
IF sy-subrc = 0.
lr_class->get_interface_type(
EXPORTING
p_name = 'IF_T100_MESSAGE'
RECEIVING
p_descr_ref = lr_intf
EXCEPTIONS
interface_not_found = 1
OTHERS = 2 ).
IF sy-subrc = 0.
lr_msg ?= ir_exception. "Cast to interface
l_bapiret2-id = lr_msg->t100key-msgid.
l_bapiret2-number = lr_msg->t100key-msgno.
cl_message_helper=>set_msg_vars_for_if_t100_msg( text = lr_msg ).
l_bapiret2-message_v1 = sy-msgv1.
l_bapiret2-message_v2 = sy-msgv2.
l_bapiret2-message_v3 = sy-msgv3.
l_bapiret2-message_v4 = sy-msgv4.
l_bapiret2-message = me->get_msg(
i_msgid = l_bapiret2-id
i_msgno = l_bapiret2-number ).
ENDIF.
ENDIF.
CATCH cx_root.
"Pokémon exception handling
ENDTRY.
"No T100KEY Interface available
IF lr_msg IS INITIAL.
l_bapiret2-message = ir_exception->if_message~get_text( ).
l_bapiret2-message_v1 = sy-msgv1.
l_bapiret2-message_v2 = sy-msgv2.
l_bapiret2-message_v3 = sy-msgv3.
l_bapiret2-message_v4 = sy-msgv4.
ENDIF.
ENDMETHOD.
Hope this helps as I struggled with the same problem. Maybe there is some adjustment needed, but I think you get the basic idea. This method can handle
I might be missing something, but can't you just use IF_MESSAGE~GET_TEXT which is present on CX_ROOT ?
Otherwise, I would make it the responsibility of the custom exception class to have a method that can return a proper message ( it might rely on the utility method you are planning on ).