sqlplsqloracle-sqldeveloperoracle-rest-data-services

ORA-06502: PL/SQL when huge data is returned with ORDS (21c ATP)


I'm making use of below PLSQL procedure to return the CLOB data to my ORDS handler (ATP 21c database) but when the huge data (like more than 70 rows of header and lines) is returned then I'm getting error as below. ( NOTE : This is working fine for less data )

ERROR as below:

The request could not be processed because an error occurred whilst attempting to evaluate a SQL statement associated with this resource. Verify that the URI and payload are correctly specified for the requested operation. If the issue persists then please contact the author of the resource. SQL Error Code: 6502, Error Message: ORA-06502: PL/SQL: value or conversion error ORA-06512: at line 9 https://docs.oracle.com/error-help/db/ora-06502/

My PLSQL procedure:

CREATE OR REPLACE PROCEDURE GET_PO_DETAILS(po_response OUT CLOB) AS
    l_po_header_json CLOB;
BEGIN
    -- Create temporary LOBs
    DBMS_LOB.CREATETEMPORARY(po_response, TRUE);
    DBMS_LOB.CREATETEMPORARY(l_po_header_json, TRUE);

    -- Main query: Aggregate headers and lines into a JSON array
    SELECT JSON_ARRAYAGG( -- Outer JSON_ARRAYAGG returns entire CLOB
        JSON_OBJECT(
            'PO_HEADER_ID'    VALUE h.PO_HEADER_ID,
            'PO_NUMBER'       VALUE h.PO_NUMBER,
            'VENDOR_NAME'     VALUE h.VENDOR_NAME,
            'ORDER_DATE'      VALUE TO_CHAR(h.ORDER_DATE, 'YYYY-MM-DD'),
            'TOTAL_AMOUNT'    VALUE h.TOTAL_AMOUNT,
            'STATUS'          VALUE h.STATUS,
            'CREATED_BY'      VALUE h.CREATED_BY,
            'CREATED_DATE'    VALUE TO_CHAR(h.CREATED_DATE, 'YYYY-MM-DD'),
            'UPDATED_BY'      VALUE h.UPDATED_BY,
            'UPDATED_DATE'    VALUE TO_CHAR(h.UPDATED_DATE, 'YYYY-MM-DD'),
            'LINES' VALUE (
                SELECT JSON_ARRAYAGG(
                    JSON_OBJECT(
                        'PO_LINE_ID'       VALUE l.PO_LINE_ID,
                        'LINE_NUMBER'      VALUE l.LINE_NUMBER,
                        'ITEM_CODE'        VALUE l.ITEM_CODE,
                        'ITEM_DESCRIPTION' VALUE l.ITEM_DESCRIPTION,
                        'QUANTITY'         VALUE l.QUANTITY,
                        'UNIT_PRICE'       VALUE l.UNIT_PRICE,
                        'LINE_TOTAL'       VALUE l.LINE_TOTAL,
                        'CREATED_BY'       VALUE l.CREATED_BY,
                        'CREATED_DATE'     VALUE TO_CHAR(l.CREATED_DATE, 'YYYY-MM-DD'),
                        'UPDATED_BY'       VALUE l.UPDATED_BY,
                        'UPDATED_DATE'     VALUE TO_CHAR(l.UPDATED_DATE, 'YYYY-MM-DD')
                    )
                )
                FROM PO_LINES l
                WHERE l.PO_HEADER_ID = h.PO_HEADER_ID
            )
        )
    RETURNING CLOB
    )
    INTO l_po_header_json
    FROM PO_HEADER h;

    -- Assign final result
    DBMS_LOB.APPEND(po_response, l_po_header_json);

EXCEPTION
    WHEN OTHERS THEN
        DBMS_LOB.CREATETEMPORARY(po_response, TRUE);
        DBMS_LOB.WRITE(po_response, LENGTH(SQLERRM) + 100, 1,
            '{"status": "ERROR", "message": "Error while fetching PO details: ' || SQLERRM || '"}'
        );
END GET_PO_DETAILS;

My ORDS Get template handler block as below:

BEGIN
  ORDS.ENABLE_SCHEMA(
      p_enabled             => TRUE,
      p_schema              => 'ADMIN',
      p_url_mapping_type    => 'BASE_PATH',
      p_url_mapping_pattern => 'admin',
      p_auto_rest_auth      => FALSE);    

  ORDS.DEFINE_MODULE(
      p_module_name    => 'bl',
      p_base_path      => '/bl/',
      p_items_per_page =>  25,
      p_status         => 'PUBLISHED',
      p_comments       => NULL);      
  ORDS.DEFINE_TEMPLATE(
      p_module_name    => 'bl',
      p_pattern        => 'getData',
      p_priority       => 0,
      p_etag_type      => 'HASH',
      p_etag_query     => NULL,
      p_comments       => NULL);
  ORDS.DEFINE_HANDLER(
      p_module_name    => 'bl',
      p_pattern        => 'getData',
      p_method         => 'GET',
      p_source_type    => 'plsql/block',
      p_items_per_page =>  25,
      p_mimes_allowed  => '',
      p_comments       => NULL,
      p_source         => 
'declare
    p_po_number VARCHAR2(100) := ''PO1005'';
    -- A CLOB variable that holds the JSON content
    v_clob CLOB;
    -- JSON object to hold the parsed JSON
    l_response JSON_OBJECT_T;
begin
    GET_PO_DETAILS(po_response => v_clob);
    --l_response := JSON_OBJECT_T.parse(v_clob);
    owa_util.mime_header (''application/json'', true); 
    htp.p(v_clob);
END;'
      );

  COMMIT; 
END;

My Tables:

-- Creating the PO_HEADER table
CREATE TABLE PO_HEADER (
    PO_HEADER_ID     NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 
    PO_NUMBER        VARCHAR2(50) UNIQUE, 
    VENDOR_NAME      VARCHAR2(100),
    ORDER_DATE       DATE DEFAULT SYSDATE,
    TOTAL_AMOUNT     NUMBER(15,2) DEFAULT 0,
    STATUS           VARCHAR2(20) DEFAULT 'DRAFT',
    CREATED_BY       VARCHAR2(50),
    CREATED_DATE     DATE DEFAULT SYSDATE,
    UPDATED_BY       VARCHAR2(50),
    UPDATED_DATE     DATE
);

-- Creating the PO_LINES table without NOT NULL constraints
CREATE TABLE PO_LINES (
    PO_LINE_ID       NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, 
    PO_HEADER_ID     NUMBER,  -- No FK constraint
    LINE_NUMBER      NUMBER,  -- Sequential line number
    ITEM_CODE        VARCHAR2(50),
    ITEM_DESCRIPTION VARCHAR2(200),
    QUANTITY         NUMBER(10,2) DEFAULT 1 CHECK (QUANTITY > 0),
    UNIT_PRICE       NUMBER(10,2) DEFAULT 0 CHECK (UNIT_PRICE >= 0),
    LINE_TOTAL       NUMBER(15,2) GENERATED ALWAYS AS (QUANTITY * UNIT_PRICE) VIRTUAL, 
    CREATED_BY       VARCHAR2(50),
    CREATED_DATE     DATE DEFAULT SYSDATE,
    UPDATED_BY       VARCHAR2(50),
    UPDATED_DATE     DATE
);

Solution

  • You have several issues:

    1. QUANTITY has the data-type NUMBER(10,2) (which leads to the question, is 0.01 a valid quantity? Or should the column have the data-type NUMBER(8,0)?) and UNIT_PRICE has the data-type NUMBER(10,2) but LINE_TOTAL is a generated column defined as QUANTITY*UNIT_PRICE but it only has the scale and precision NUMBER(15,2). If you have the maximum allowed quantity and price (99999999.99) then that does not fit in the LINE_TOTAL data-type. You need to change the table to LINE_TOTAL NUMBER(20,4).
    2. You need RETURNING CLOB everywhere where the JSON could be larger than 4000 bytes.

    Like this:

      SELECT JSON_ARRAYAGG( -- Outer JSON_ARRAYAGG returns entire CLOB
               JSON_OBJECT(
                 'PO_HEADER_ID'    VALUE h.PO_HEADER_ID,
                 'PO_NUMBER'       VALUE h.PO_NUMBER,
                 'VENDOR_NAME'     VALUE h.VENDOR_NAME,
                 'ORDER_DATE'      VALUE TO_CHAR(h.ORDER_DATE, 'YYYY-MM-DD'),
                 'TOTAL_AMOUNT'    VALUE h.TOTAL_AMOUNT,
                 'STATUS'          VALUE h.STATUS,
                 'CREATED_BY'      VALUE h.CREATED_BY,
                 'CREATED_DATE'    VALUE TO_CHAR(h.CREATED_DATE, 'YYYY-MM-DD'),
                 'UPDATED_BY'      VALUE h.UPDATED_BY,
                 'UPDATED_DATE'    VALUE TO_CHAR(h.UPDATED_DATE, 'YYYY-MM-DD'),
                 'LINES' VALUE (
                   SELECT JSON_ARRAYAGG(
                            JSON_OBJECT(
                              'PO_LINE_ID'       VALUE l.PO_LINE_ID,
                              'LINE_NUMBER'      VALUE l.LINE_NUMBER,
                              'ITEM_CODE'        VALUE l.ITEM_CODE,
                              'ITEM_DESCRIPTION' VALUE l.ITEM_DESCRIPTION,
                              'QUANTITY'         VALUE l.QUANTITY,
                              'UNIT_PRICE'       VALUE l.UNIT_PRICE,
                              'LINE_TOTAL'       VALUE l.LINE_TOTAL,
                              'CREATED_BY'       VALUE l.CREATED_BY,
                              'CREATED_DATE'     VALUE TO_CHAR(l.CREATED_DATE, 'YYYY-MM-DD'),
                              'UPDATED_BY'       VALUE l.UPDATED_BY,
                              'UPDATED_DATE'     VALUE TO_CHAR(l.UPDATED_DATE, 'YYYY-MM-DD')
                            )
                            RETURNING CLOB
                          )
                   FROM   PO_LINES l
                   WHERE  l.PO_HEADER_ID = h.PO_HEADER_ID
                 )
                 RETURNING CLOB
               )
               RETURNING CLOB
             )
      INTO   l_po_header_json
      FROM   PO_HEADER h;
    

    Note: I do not think the JSON_OBJECT for PO_LINES can reach 4000 bytes so it does not need RETURNING CLOB but its aggregation and PO_HEADER objects can.

    fiddle