sqlplsqloracle-rest-data-services

ORDS PL/SQL block is unable to parse the input JSON and merge or delete the records


I'm trying to create one ORDS REST API in my 19c oracle ATP database, i.e. to accept the JSON input payload in my RESTG request (POST HTTP method) and try to insert, update, delete the header information and lines but the PL/SQL block what I have written is not working in 19c, can someone please help me to fix this case

Getting error in PL.SQL like below:

ERROR at line 2: ORA-06550: line 2, column 13: PLS-00201: identifier 'JSON' must be declared ORA-06550: line 0, column 1:

These are 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
);

-- Creating indexes for performance
CREATE INDEX IDX_PO_HEADER_DATE ON PO_HEADER (ORDER_DATE);
CREATE INDEX IDX_PO_LINES_HEADER_ID ON PO_LINES (PO_HEADER_ID); 

ORDS PL/SQL block is below:

DECLARE
    v_json  JSON := JSON(:body);  -- ORDS passes JSON as CLOB, converted to JSON type
    v_poNumber VARCHAR2(50);
    v_operation VARCHAR2(10);
    v_header_id NUMBER;
BEGIN
    -- Extract header-level values
    v_poNumber := v_json.poNumber;
    v_operation := v_json.operation;

    -- Handle Header Operations
    IF v_operation = 'add' THEN
        INSERT INTO PO_HEADER (PO_NUMBER, VENDOR_NAME, ORDER_DATE, TOTAL_AMOUNT, STATUS, CREATED_BY)
        VALUES (
            v_json.poNumber,
            v_json.vendorName,
            TO_DATE(v_json.orderDate, 'YYYY-MM-DD'),
            v_json.totalAmount,
            v_json.status,
            v_json.createdBy
        )
        RETURNING PO_HEADER_ID INTO v_header_id;

    ELSIF v_operation = 'update' THEN
        UPDATE PO_HEADER
        SET VENDOR_NAME = v_json.vendorName,
            ORDER_DATE = TO_DATE(v_json.orderDate, 'YYYY-MM-DD'),
            TOTAL_AMOUNT = v_json.totalAmount,
            STATUS = v_json.status,
            UPDATED_BY = v_json.createdBy,
            UPDATED_DATE = SYSDATE
        WHERE PO_NUMBER = v_poNumber
        RETURNING PO_HEADER_ID INTO v_header_id;

    ELSIF v_operation = 'remove' THEN
        DELETE FROM PO_HEADER WHERE PO_NUMBER = v_poNumber;
        DELETE FROM PO_LINES WHERE PO_HEADER_ID = (SELECT PO_HEADER_ID FROM PO_HEADER WHERE PO_NUMBER = v_poNumber);
        RETURN;
    END IF;

    -- Process PO Lines Using JSON_TABLE with JSON Type
    FOR line IN (
        SELECT *
        FROM JSON_TABLE(v_json.poLines, '$[*]'
            COLUMNS (
                operation VARCHAR2(10) PATH '$.operation',
                lineNumber NUMBER PATH '$.lineNumber',
                itemCode VARCHAR2(50) PATH '$.itemCode',
                itemDescription VARCHAR2(200) PATH '$.itemDescription',
                quantity NUMBER PATH '$.quantity',
                unitPrice NUMBER PATH '$.unitPrice',
                createdBy VARCHAR2(50) PATH '$.createdBy'
            )
        )
    ) LOOP
        IF line.operation = 'Add' THEN
            INSERT INTO PO_LINES (PO_HEADER_ID, LINE_NUMBER, ITEM_CODE, ITEM_DESCRIPTION, QUANTITY, UNIT_PRICE, CREATED_BY)
            VALUES (v_header_id, line.lineNumber, line.itemCode, line.itemDescription, line.quantity, line.unitPrice, line.createdBy);

        ELSIF line.operation = 'Update' THEN
            UPDATE PO_LINES
            SET ITEM_CODE = line.itemCode,
                ITEM_DESCRIPTION = line.itemDescription,
                QUANTITY = line.quantity,
                UNIT_PRICE = line.unitPrice,
                UPDATED_BY = line.createdBy,
                UPDATED_DATE = SYSDATE
            WHERE PO_HEADER_ID = v_header_id AND LINE_NUMBER = line.lineNumber;

        ELSIF line.operation = 'Remove' THEN
            DELETE FROM PO_LINES WHERE PO_HEADER_ID = v_header_id AND LINE_NUMBER = line.lineNumber;
        END IF;
    END LOOP;

    COMMIT;
END;

My JSON input payload:

{
    "poNumber": "PO1004",
    "vendorName": "Tech Solutions",
    "orderDate": "2025-03-01",
    "totalAmount": 800.75,
    "status": "APPROVED",
    "createdBy": "User3",
    "operation": "add",
    "poLines": [
        {
            "operation": "Add",
            "lineNumber": 1,
            "itemCode": "ITEM005",
            "itemDescription": "Keyboard",
            "quantity": 4,
            "unitPrice": 50.00,
            "createdBy": "User3"
        },
        {
            "operation": "Remove",
            "lineNumber": 2,
            "itemCode": "ITEM006",
            "itemDescription": "Mouse Pad",
            "quantity": 10,
            "unitPrice": 8.50,
            "createdBy": "User3"
        },
        ,
        {
            "operation": "update",
            "lineNumber": 2,
            "itemCode": "ITEM006",
            "itemDescription": "Mouse Pad",
            "quantity": 10,
            "unitPrice": 8.50,
            "createdBy": "User3"
        }
    ]
}

Solution

  • v_json  JSON := JSON(:body);
    

    Fails because you are using Oracle 19c and the JSON data type was not introduced until Oracle 21c.

    any workaround is there for this ?

    Use:

    v_json CLOB := :body;
    

    Then use JSON_TABLE, as you do later in the PL/SQL block to parse the JSON string into variables.

    DECLARE
      v_json         CLOB := :body;
      v_poNumber     PO_HEADER.PO_NUMBER%TYPE;
      v_operation    VARCHAR2(10);
      v_header_id    PO_HEADER.PO_HEADER%TYPE;
      v_total_amount PO_HEADER.TOTAL_AMOUNT%TYPE;
      v_vendor_name  PO_HEADER.VENDOR_NAME%TYPE;
      v_status       PO_HEADER.STATUS%TYPE;
      v_created_by   PO_HEADER.CREATED_BY%TYPE;
    BEGIN
      SELECT po_number
             -- etc.
      INTO   v_poNumber
             -- etc.
      FROM   JSON_TABLE(
               v_json,
               '$'
               COLUMNS (
                 po_number VARCHAR2(50) PATH '$.poNumber'
                 -- etc.
               )
             );
    

    Then use the variables in your later INSERT/UPDATE statements.