0

I'm having issue with finding right way to cast my user-defined function in PL/pgSQL into jOOQ code. My user-defined function in PL/pgSQL returns JSON type and I need to somehow adjust/cast it in jOOQ. I've Googled examples, but found none.

Just in case here is my user-defined function in PL/pgSQL:

create or replace function public.get_order_by_order_id(o_id bigint) returns json as
$BODY$
DECLARE
    total_oi_price double precision;
    book_price double precision;
    total_price double precision;
    oi_amount integer;
    order_items json;
    item_recs RECORD;
    book_json json;
    single_order_item json;
    found_order "vertx-jooq-cr".public.orders;
    found_user json;
    _item_id bigint;
    item_array json[];
BEGIN
    select * into found_order
    from "vertx-jooq-cr".public.orders
    where order_id = o_id;

    select json_build_object('user_id', "vertx-jooq-cr".public.users.user_id, 'username', "vertx-jooq-cr".public.users.username)
    into found_user
    from "vertx-jooq-cr".public.users
    INNER JOIN "vertx-jooq-cr".public.orders as o USING (user_id)
    WHERE o.order_id = o_id;

    total_price = 0.00;

    FOR item_recs IN SELECT *
        FROM public.order_item AS oi WHERE oi.order_id = o_id
    LOOP
        select public.get_book_by_book_id(item_recs.book_id) into book_json
        from public.order_item
        where public.order_item.order_item_id IN (item_recs.order_item_id);

        select price INTO book_price FROM book AS b WHERE b.book_id = item_recs.book_id;
        select amount INTO oi_amount FROM order_item AS oi WHERE oi.amount = item_recs.amount;
        
        total_oi_price = book_price * oi_amount;

        SELECT json_build_object('order_item_id', item_recs.order_item_id,
        'amount', item_recs.amount,
        'book', book_json,
        'order_id', item_recs.order_id,
        'total_order_item_price', trunc(total_oi_price::double precision::text::numeric, 2)) INTO single_order_item;
        total_price := total_price + total_oi_price;
        item_array = array_append(item_array, single_order_item);
    END LOOP;
    order_items = array_to_json(item_array);

    return (select json_build_object(
        'order_id', found_order.order_id,
        'total_price', trunc(total_price::double precision::text::numeric, 2),
        'order_date', found_order.order_date,
        'user', found_user,
        'order_items', order_items
    ));

end;
$BODY$
LANGUAGE 'plpgsql';

...and another one which is using function listed above.

CREATE OR REPLACE FUNCTION get_all_orders() RETURNS JSON AS
$BODY$
DECLARE
    single_order RECORD;
    single_order_json json;
    orders_array json[];
BEGIN

    FOR single_order IN SELECT * FROM public.orders ORDER BY order_id
    LOOP
        SELECT get_order_by_order_id(single_order.order_id) INTO single_order_json;
        orders_array = array_append(orders_array, single_order_json);
    END LOOP;

    return (select json_build_object(
        'orders', orders_array
    ));
END;
$BODY$
LANGUAGE 'plpgsql';

Both functions has been successfully code-generated in my Maven project and last one get_all_orders() is needed to perform SELECT operation on it and return JSON object in my jOOQ code.

Here is Routines.java class in **.jooq package which is generated among Keys.java, DefaultCatalog.java and other classes:

/**
 * Convenience access to all stored procedures and functions in public
 */
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class Routines {

    /**
     * Call <code>public.get_all_orders</code>
     */
    public static JSON getAllOrders(Configuration configuration) {
        GetAllOrders f = new GetAllOrders();

        f.execute(configuration);
        return f.getReturnValue();
    }

    /**
     * Get <code>public.get_all_orders</code> as a field.
     */
    public static Field<JSON> getAllOrders() {
        GetAllOrders f = new GetAllOrders();

        return f.asField();
    }

// other methods left out for code brevity

/**
     * Call <code>public.get_order_by_order_id</code>
     */
    public static JSON getOrderByOrderId(Configuration configuration, Long oId) {
        GetOrderByOrderId f = new GetOrderByOrderId();
        f.setOId(oId);

        f.execute(configuration);
        return f.getReturnValue();
    }

    /**
     * Get <code>public.get_order_by_order_id</code> as a field.
     */
    public static Field<JSON> getOrderByOrderId(Long oId) {
        GetOrderByOrderId f = new GetOrderByOrderId();
        f.setOId(oId);

        return f.asField();
    }

    /**
     * Get <code>public.get_order_by_order_id</code> as a field.
     */
    public static Field<JSON> getOrderByOrderId(Field<Long> oId) {
        GetOrderByOrderId f = new GetOrderByOrderId();
        f.setOId(oId);

        return f.asField();
    }
}

And here is my GetAllOrders.java routine class located in **.jooq.routines package

/**
 * This class is generated by jOOQ.
 */
@SuppressWarnings({ "all", "unchecked", "rawtypes" })
public class GetAllOrders extends AbstractRoutine<JSON> {

    private static final long serialVersionUID = 917599810;

    /**
     * The parameter <code>public.get_all_orders.RETURN_VALUE</code>.
     */
    public static final Parameter<JSON> RETURN_VALUE = Internal.createParameter("RETURN_VALUE", org.jooq.impl.SQLDataType.JSON, false, false);

    /**
     * Create a new routine call instance
     */
    public GetAllOrders() {
        super("get_all_orders", Public.PUBLIC, org.jooq.impl.SQLDataType.JSON);

        setReturnParameter(RETURN_VALUE);
    }
}

Finally, this is my jOOQ code for performing SELECT query in jOOQ:

Future<JsonObject> ordersFuture = queryExecutor.transaction(qe -> qe
                .query(dsl -> dsl
                    .select(new Routines().getAllOrders())
            )); 

...definition of qe.query() method is given below:

@Override
    public <R extends Record> Future<QueryResult> query(Function<DSLContext, ? extends ResultQuery<R>> queryFunction) {
        return executeAny(queryFunction).map(ReactiveQueryResult::new);
    }

Produced problems:

"Type mismatch: cannot convert from Future < Object > to Future < JsonObject > "

"Type mismatch: cannot convert from Future < QueryResult > to Future < Object > "

BTW, I've need to mention this is vertx-jooq implementation which uses jOOQ 3.13.1.
Any help/suggestion is greatly appreciated.

UPDATE:
As requested here are missing types and signatures 1st for transaction() method (more info here )

/**
     * Convenience method to perform multiple calls on a transactional QueryExecutor, committing the transaction and
     * returning a result.
     * @param transaction your code using a transactional QueryExecutor.
     *                    <pre>
     *                    {@code
     *                    ReactiveClassicGenericQueryExecutor nonTransactionalQueryExecutor...;
     *                    Future<QueryResult> resultOfTransaction = nonTransactionalQueryExecutor.transaction(transactionalQueryExecutor ->
     *                      {
     *                          //make all calls on the provided QueryExecutor that runs all code in a transaction
     *                          return transactionalQueryExecutor.execute(dslContext -> dslContext.insertInto(Tables.XYZ)...)
     *                              .compose(i -> transactionalQueryExecutor.query(dslContext -> dslContext.selectFrom(Tables.XYZ).where(Tables.XYZ.SOME_VALUE.eq("FOO")));
     *                      }
     *                    );
     *                    }
     *                    </pre>
     * @param <U> the return type.
     * @return the result of the transaction.
     */
    public <U> Future<U> transaction(Function<ReactiveClassicGenericQueryExecutor, Future<U>> transaction){
        return beginTransaction()
                .compose(queryExecutor -> transaction.apply(queryExecutor) //perform user tasks
                        .compose(res -> queryExecutor.commit() //commit the transaction
                                .map(v -> res))); //and return the result
    }

...and executeAny() (more info available here ):

 /**
     * Executes the given queryFunction and returns a <code>RowSet</code>
     * @param queryFunction the query to execute
     * @return the results, never null
     */
    public Future<RowSet<Row>> executeAny(Function<DSLContext, ? extends Query> queryFunction) {
        Query query = createQuery(queryFunction);
        log(query);
        Promise<RowSet<Row>> rowPromise = Promise.promise();
        delegate.preparedQuery(toPreparedQuery(query)).execute(getBindValues(query),rowPromise);
        return rowPromise.future();
    }

...and here for ReactiveQueryResult

UPDATE2:
This is my result of get_all_orders() function created in PL/pgSQL of JSON type:

{
  "orders": [
    {
      "order_id": 1,
      "total_price": 29.99,
      "order_date": "2019-08-22T10:06:33",
      "user": {
        "user_id": 1,
        "username": "test"
      },
      "order_items": [
        {
          "order_item_id": 1,
          "amount": 1,
          "book": {
            "book_id": 1,
            "title": "Harry Potter and the Philosopher's Stone",
            "price": 29.99,
            "amount": 400,
            "is_deleted": false,
            "authors": [
              {
                "author_id": 4,
                "first_name": "JK",
                "last_name": "Rowling"
              }
            ],
            "categories": [
              {
                "category_id": 2,
                "name": "Lyric",
                "is_deleted": false
              }
            ]
          },
          "order_id": 1,
          "total_order_item_price": 29.99
        }
      ]
    },
    {
      "order_id": 2,
      "total_price": 29.99,
      "order_date": "2019-08-22T10:10:13",
      "user": {
        "user_id": 1,
        "username": "test"
      },
      "order_items": [
        {
          "order_item_id": 2,
          "amount": 1,
          "book": {
            "book_id": 1,
            "title": "Harry Potter and the Philosopher's Stone",
            "price": 29.99,
            "amount": 400,
            "is_deleted": false,
            "authors": [
              {
                "author_id": 4,
                "first_name": "JK",
                "last_name": "Rowling"
              }
            ],
            "categories": [
              {
                "category_id": 2,
                "name": "Lyric",
                "is_deleted": false
              }
            ]
          },
          "order_id": 2,
          "total_order_item_price": 29.99
        }
      ]
    }
  ]
}
NikolaS
  • 503
  • 2
  • 8
  • 20
  • Can you show the jOOQ code you were using, and the problems it produced? – Lukas Eder Jun 23 '20 at 16:38
  • Hi Lukas, I've updated my question. Please take a look. – NikolaS Jun 23 '20 at 17:05
  • OK, so it's not related to JSON. Can you provide a fully self contained example? The current question is still missing types and signatures, such as `queryExecutor.transaction`, `executeAny(...)`, `ReactiveQueryResult`, ... – Lukas Eder Jun 23 '20 at 17:11
  • I've updated my question by adding method signatures (including their bodies) and also links to GitHub repo where source code can be seen. BTW, is it possible that something like in [this](https://github.com/jOOQ/jOOQ/issues/8106#issuecomment-446130836) issue needs to be done...maybe a custom converter? – NikolaS Jun 23 '20 at 17:45

1 Answers1

3

From the middle of your question:

Future<JsonObject> ordersFuture = queryExecutor.transaction(qe -> qe.query(...)); 

// Where
<R extends Record> Future<QueryResult> query(
  Function<DSLContext, ? extends ResultQuery<R>> queryFunction
) { ... }

The Java compiler cannot convert your Future<QueryResult> to a Future<JsonObject>. There may be some additional type inference problems that leads to a confusing error message that doesn't tell this as it is, but that's what I'm seeing.

You have to somehow explicitly map from Future<QueryResult> to Future<JsonObject>. In any case, the transaction() method doesn't do this conversion:

<U> Future<U> transaction(
  Function<ReactiveClassicGenericQueryExecutor, Future<U>> transaction
) { ... }

The <U> type here gets bound to QueryResult from your query() method.

Lukas Eder
  • 211,314
  • 129
  • 689
  • 1,509
  • Hi Lukas and thank you for quick response. So what you are saying it's not something like in this [issue](https://github.com/jOOQ/jOOQ/issues/8106) where `sql()` method (from class which implements `Binding` interface) needs to be edited to use `json` type instead of `jsonb` type (I'm referencing your reply in that mentioned issue)? – NikolaS Jun 25 '20 at 07:03
  • I've updated my question where I added result of `SELECT get_all_orders();` (i.e. PL/pgSQL function) – NikolaS Jun 25 '20 at 07:20
  • Also, I've listened to your advice and replace `JsonObject` with `QueryResult` type in `Future ordersFuture` variable and errors are gone! :) Now what I've asked in my previous comment is there need to use Converter or Binding class (by this [page](https://www.jooq.org/doc/3.13/manual/sql-building/queryparts/custom-bindings/) I presume I need to use Converter, right)? – NikolaS Jun 25 '20 at 08:13
  • @NikolaS: I'm getting more and more confused by this question :) See, I'm not sitting with you at your computer, and I don't know what things you have figured out in the meantime, and what open questions you may still have. Your question as it is edited now combines your entire history of progress in this ... "session", but if someone visits this question in the future, they will not understand what the question was, or the answer. May I please ask you to ask shorter, conciser questions about individual problems? Here's a useful resource for that: https://stackoverflow.com/help/how-to-ask – Lukas Eder Jun 25 '20 at 11:24
  • You are right and I apologize, I'll create another question because my question(s) in comments of your answer are out of scope now. :) – NikolaS Jun 25 '20 at 12:20
  • Here's the [link](https://stackoverflow.com/q/62575780/6805866) to another created question which (I hope) clarifies what I'm trying to achieve. Please take a look. – NikolaS Jun 25 '20 at 12:52