JOOQ and PostgreSQL - call stored procedure with bytea parameter - java

I have a toy stored procedure in postgresql.
CREATE FUNCTION foo(bar bytea) RETURNS BOOLEAN AS
$$
BEGIN
RETURN TRUE;
END;
$$
LANGUAGE plpgsql;
Since I want to call it from Java code, I've generated Java object representing database routine using jooq-codegen-maven plugin. When I call it:
def select = DSL.select(Routines.test([1,2,3] as byte[]))
context.batch(s).execute()
it fails due to lack of procedure matching given name and list of parameters' types. It turns out that jooq generates incorrect query:
select "public"."test"(X'010203');
function public.test(bit) does not exist
Should be:
select "public"."test"('\x010203');
or:
select "public"."test"(E'\\01\\02\\03'::bytea);
Do you have any suggestion for a workaround? The only requirement is to have bytea in the end, because I want to insert a row with bytea column
into database.
The similar situation is for smallints. Jooq generates incorrect query (does not cast value ::smallint), so postgresql cannot match any procedure due to the type mismatch (integer given, but smallint expected). This case can be easily "solved" by changing parameter type to int and making the case in procedure's body.

Related

How to query a boolean column using an integer with Postgres?

I post a similar question previously, but have opened another question to be more specific as the previous one gave me a solution but I have now encountered another problem.
We have an existing Oracle database that had boolean columns defined like so
CREATE TABLE MY_TABLE
(
SOME_TABLE_COLUMN NUMBER(1) DEFAULT 0 NOT NULL,
etc..
And with the corresponding java field defined like private boolean someTableColumn; I've come to understand this is because Oracle does not have a Boolean datatype, but under the hood does the conversion from boolean to integer when inserting data, and the reverse when retriving data.
This has caused an issue when I have been working on migrating our database from Oracle to Postgres. Thanks to answers on my previous question, I have migrated the column type from NUMBER(1) to BOOLEAN. This has solved the issue with inserting data. However, our codebase uses JDBCTemplate and we unfortunately have hundereds of hardcoded queries in the code that make queries like SELECT * FROM MY_TABLE WHERE TABLE_COLUMN=1.
When these run against the Postgres DB, I get the following error ERROR: operator does not exist: boolean = integer. We have a requirement to have backwards compatability with Oracle, so I can't simply update these queries to replace 1 and 0 with TRUE and FALSE respectively.
Is there a way I can configure Postgres so it can do a conversion behind the scenes to resolve this? I have looked at casts but I don't really understand the documentation and the examples given don't seem to match my use case. Any help is appreciated.
Can you try to use '0' and '1' instead of 0 and 1 in your requests ?
I used to work on apps compliant with both Oracle and Postgresql using this syntax. Apps were using JPA but can say with certainty that we were also using this syntax with nativeQuery = true.
Note: I would have posted this as a comment but I don't have the required reputation to do so, hence the post as an answer
Update:
Duh! Brain Freeze. On later thought there is a way to get this conversion in both directions. Process via a View.
Steps:
Rename your table.
Create a view having the same name as the old table. In this view
translate the boolean column to the appropriate integer.
Create an trigger function and an instead of trigger on the view for insert/update dml. In the trigger function translate the column value to boolean as appropriate.
See revised demo.
alter table testb rename to testb_tab;
create or replace view testb (id, name, is_ok)
as
select i,
, name
, is_ok::int
from testb_tab;
create or replace
function testb_act_dml()
returns trigger
language plpgsql
as $$
begin
if tg_op = 'INSERT' then
insert into testb_tab(name,is_ok)
values (new.name, new.is_ok::boolean) ;
else
update testb_tab
set name = new.name
, is_ok = new.is_ok::boolean
where id = old.id;
end if;
return new;
end;
$$;
create trigger testb_biuri -- before insert update row instead of
instead of insert or update on testb
for each row execute function testb_act_dml();
Finally, there is another option which probably has the lease work. Do not change the column description to Boolean. Either leave it as an integer or define it as a smallint. Either way a check constraint may come in useful. So something along the line of:
create table tests( id int generated always as identity primary key
, name text
, is_ok smallint
, constraint is_ok_ck
check ( is_ok in (0,1) or is_ok is null)
);
This is one of the issues I have with the concept of "database independence". It simply does not exist. Vendors implementation often simply vary too much. In this case Postures to the rescue, perhaps but also perhaps extreme: create you own create your own operators. Proceed with caution:
-- function to compare boolean = integer
create or replace
function"__bool=int"( b boolean, i int)
returns boolean
language sql
as $$
select (b=i::boolean);
$$;
-- create the Operator for boolean = integer
create operator = (
leftarg = boolean
, rightarg = int
, function = "__bool=int"
, commutator = =
);
The above will not allow your code: SELECT * FROM MY_TABLE WHERE TABLE_COLUMN=1 (see demo here).
However, this road may lead to unexpected twists, and lots of function/operator pairs. For example the above does not support SELECT * FROM MY_TABLE WHERE TABLE_COLUMN<>1. That requires another function/operator combination. Further I do not see a retrieval function that converts a boolean back to an integer. If you follow this path be sure to massively test your boolean-to-integer (integer-to-boolean) operations. It may just be better to just byte the bullet and updated those few queries (you did say hundreds not thousands) as #mlogario suggests.

How to using like array of string HIBERNATE

I'm trying to pass an array of string as a parameter to my query but I get the following error
ERROR: operator does not exist: text ~~ record Dica: No operator matches the given name and argument type(s). You might need to add explicit type casts.
Here is my query
select * from table where value like any (array[?1]);
when I run it using hibernate the query is like:
select * from table where value like any (array[('%foo%', '%bar%', '%baz%')]);
There's a best way to pass my array as parameter?? I think that is important to say that my array is dynamic so i can't fiz it in my query.
use setParameterList method.
String queryStr = "select * from table where value in :valueList";
Query query = SessionFactory.getCurrentSession().createQuery(queryStr);
query.setParameterList("valueList", new Object[]{"%foo%","%bar%","%baz%"});
Please note that I used in clause and it doesn't support wildcard characters such as %.
First use createNativeQuery instead of createQuery as syntax is native to PSQL.
Second query syntax should be select * from table where value like any (array?1) as ?1 will be substituted by ['%foo%', '%bar%', '%baz%'], so your end query will match required PSQL syntax.
select * from table where value like any (array['%foo%', '%bar%', '%baz%'])
Firstly, your syntax is wrong.
Instead of:
select * from table where value like any (array[?1]);
You should use:
select * from table where value like any (:vals);
You cannot use array[?] or array[:var] to construct the variable. That is invalid syntax.
Secondly for Hibernate 5.2 up to 5.4 you can just add this dependency and you can use the most common object arrays directly. Primitive arrays are not supported and are not supposed to be.

Modifying PLSQL function to return multiple rows from same column

I am a beginner PLSQL user, and I have what might be a rather simple question.
I have created the following SQL Function, which returns the created date of the process whose corporate ID matches the corporate ID that I have given it. I have this connected to my JDBC, and it returns values just fine.
However, I just realized I overlooked an important issue--it's entirely possible that more than one process will have a corporate ID that matches the ID value I've inputted, and in cases like that I will need to be able to access all of the created date values where the IDs return a match.
CREATE OR REPLACE FUNCTION FUNCTION_1(
c_id IN INT)
RETURN INT
AS
p_date process.date_created%TYPE;
BEGIN
SELECT process.date_created
FROM PROCESS
WHERE process.corporate_id = c_id
ORDER BY process.corporate_id;
RETURN p_id;
END FUNCTION_1;
/
Is there a way that I can modify my function to return multiple rows from the same column, and then call that function to return some sort of array using JDBC? Or, if that isn't possible, is there a way I can return what I need using PLSQL procedures or just plain SQL combined with JDBC? I've looked through other questions here, but none seemed to be about quite what I need to know.
Thanks to anyone who can help!
you need make some changes in your function. on java side it will be simple select
you need change type of your function from int to collection
read about the table functions here Table Functions
user oracle table() function to convert result of your function to table
it let you use your function in queries. read more about the syntax here: Table Collections: Examples
here the example how to call your function from java:
select t.column_value as process_id
from table(FUNCTION_1(1)) t
--result
PROCESS_ID
1 1
2 2
--we need create new type - table of integers
CREATE OR REPLACE TYPE t_process_ids IS TABLE OF int;
--and make changes in function
CREATE OR REPLACE FUNCTION FUNCTION_1(
c_id IN INT)
RETURN t_process_ids
AS
l_ids t_process_ids := t_process_ids();
BEGIN
--here I populated result of select into the local variables
SELECT process.id
bulk collect into l_ids
FROM PROCESS
WHERE process.corporate_id = c_id
ORDER BY process.corporate_id;
--return the local var
return l_ids;
END FUNCTION_1;
--the script that I used for testing
create table process(id int, corporate_id int, date_created date);
insert into process values(1, 1, sysdate);
insert into process values(2, 1, sysdate);
insert into process values(3, 2, sysdate);

PostgreSQL: Issue with passing array to procedure

I have a type as:
CREATE TYPE status_record AS
(
id bigint,
status boolean
);
A procedure that does some processing with an array of type as input parameter as:
CREATE OR REPLACE FUNCTION update_status(status_list status_record[])
RETURNS text AS
$BODY$
DECLARE
BEGIN
--does some processing
return 'SUCCESS';
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100;
Finally I queried the procedure as:
select *
from update_status(cast(ARRAY[(385,false),(387,false)] as status_record[]));
Everything works fine in pgadmin. Later when I tried to call the same using Hibernate native SQL Query Ka Boom!!! The following is displayed:
org.postgresql.util.PSQLException:
ERROR: array value must start with "{" or dimension information
Final question: both ARRAY[--something] and {--something} does the same job?
Use an array literal (text representation of the array), since the array constructor ARRAY[...] has to be evaluated by Postgres:
SELECT update_status('{"(1,t)","(2,f)"}'::status_record[]);
Maybe even without the explicit cast:
SELECT update_status('{"(1,t)","(2,f)"}');
There were similar cases on SO before:
Pass array from node-postgres to plpgsql function
How to pass custom type array to Postgres function
Try to put the array and type initialization into a string, maybe you can then get around the problems with the obfuscation layer (aka ORM):
select update_status(cast('{"(1,true)", "(2,false)"}' as status_record[]));
I don't know Hibernate, so I can't tell if that will work.

Error Inserting Java Character object value into Oracle CHAR(1) column

I'm using a Spring jdbcTemplate.update(String sql, Object[] args) to execute a prepared insert statement on an Oracle database. One of the objects is a Character object containing the value 'Y', and the target column is of CHAR(1) type, but I'm receiving a
java.sql.SQLException: Invalid column type
exception.
I've debugged this backwards and forwards and there is no doubt that it is this one particular object that is causing the problem. The insert executes as expected when this Character Object is omitted.
I can also output the sql and Object[] values, copy the sql into sql developer, replace the value placeholders (?'s) with the actual values of the Objects, and the insert will work fine.
The sql (obfuscated to protect the guilty):
INSERT INTO SCHEMA.TABLE(NUMBER_COLUMN,VARCHAR_COLUMN,DATE_COLUMN,CHAR_COLUMN) VALUES (?,?,?,?);
The object values:
values[0] = [123]
values[1] = [Some String]
values[2] = [2012-04-19]
values[3] = [Y]
The combination run manually in sql developer and that works just fine:
INSERT INTO SCHEMA.TABLE(NUMBER_COLUMN,VARCHAR_COLUMN,DATE_COLUMN,CHAR_COLUMN) VALUES (123,'Some String','19-Apr-2012','Y');
The prepared statement sql itself is generated dynamically based on the non-null instance variable objects contained within a data transfer object (we want the database to handle generation of default values), so I can't accept any answers suggesting that I just rework the sql or insertion routine.
Anyone ever encountered this and can explain to me what's going on and how to fix it? It's frustratingly bizzare that I can't seem to insert a Character object into a CHAR(1) field. Any help would be much appreciated.
Sincerely, Longtime Lurker First-time Poster
There is no PreparedStatement.setXxx() that takes a character value, and the Oracle docs states that all JDBC character types map to Java Strings. Also, see http://docs.oracle.com/javase/1.3/docs/guide/jdbc/getstart/mapping.html#1039196, which does not include a mapping from Java char or Character to a JDBC type.
You will have to convert the value to a String.

Categories