Default Values in Oracle Functions - java

Assume the following function declaration:
FUNCTION ARTTEXTJN
(p_art_id in number
,p_arttextart in varchar2 default 'basis'
,p_sprache in varchar2 default null
,p_aufart in number default null
,p_fallback_arttextart in varchar2 default 'J'
)
RETURN VARCHAR2
Expect the first parameter all parameter have a default value.
jOOQ generate a package method like this:
public static Field<String> arttextjn(Field<? extends Number> pArtId, Field<String> pArttextart,
Field<String> pSprache, Field<? extends Number> pAufart, Field<String> pFallbackArttextart) {
Arttextjn f = new Arttextjn();
f.setPArtId(pArtId);
f.setPArttextart(pArttextart);
f.setPSprache(pSprache);
f.setPAufart(pAufart);
f.setPFallbackArttextart(pFallbackArttextart);
return f.asField();
}
If I want to use it in a query I have to pass null to the function:
dsl.select(KAMPARTIKEL.ARTNR, KAMPARTIKEL.ARTNRKAMP,
PaBez.arttextjn(KAMPARTIKEL.ART_ID, null, null, null, null))
But then Oracle does not use the default values.
Is there a way to tell jOOQ to generate overloaded methods with all possible combinations? Otherwise I'm not able to use that function in a select clause.

Is there a way to tell jOOQ to generate overloaded methods with all possible combinations?
No, there would be way too many combinations. Of course, you could extend the code generator yourself, but I would advise against it.
Otherwise I'm not able to use that function in a select clause.
Yes, you can use it! But not using that auxiliary method PaBez.arttextjn. It's possible to call it as a standalone function call:
Arttextjn f = new Arttextjn();
f.setPArtId(1);
f.execute();
String result = f.getReturnValue();
It should be possible to use in a SQL statement as well:
Arttextjn f = new Arttextjn();
f.setPArtId(KAMPARTIKEL.ART_ID);
var result =
dsl.select(KAMPARTIKEL.ARTNR, KAMPARTIKEL.ARTNRKAMP, f.asField())
.from(KAMPARTIKEL)
.fetch();
In your case, this should work out of the box.
Note that as of jOOQ 3.11, and in Oracle, jOOQ passes function arguments by index in this case, not by name (as it does for PostgreSQL). The generated SQL is:
select KAMPARTIKEL.ARTNR, KAMPARTIKEL.ARTNRKAMP, pa_bez.arttextjn(KAMPARTIKEL.ART_ID)
from KAMPARTIKEL
Which works because you're using only the first parameter, applying defaults to the rest. It wouldn't work if you would pass the last parameter, in case of which the generated SQL would have to use named parameters:
select
KAMPARTIKEL.ARTNR,
KAMPARTIKEL.ARTNRKAMP,
pa_bez.arttextjn(p_art_id => KAMPARTIKEL.ART_ID)
from KAMPARTIKEL
I've created an issue to fix this for jOOQ 3.12:
https://github.com/jOOQ/jOOQ/issues/8560

Related

postgres jdbc insert array of custom type

I want to insert array of custom type into postgres with jdbc.
my sql schema:
CREATE TYPE element_pk_t AS (
workspace_id BIGINT,
element_id BIGINT,
history_id BIGINT
);
my java class:
public class ElementPK {
public Long workspaceId;
public Long elementId;
public Long historyId;
}
How should I do this in java with jdbc?
I've found tutorial regarding custom type https://docs.oracle.com/javase/tutorial/jdbc/basics/sqlcustommapping.html, but array of custom type is still unclear to me.
One trivial approach is using pure string style SQL statement, but I still prefer using PreparedStatement with setObject or setArray
If I understood you correctly, then, from the Java code, you need to generate query that looks, for example, like this:
INSERT INTO parent_table(elements)
VALUES (
ARRAY[
row(1, 2, 3)::element_pk,
row(4, 5, 6)::element_pk,
row(7, 8, 9)::element_pk
]);
Assuming that you have a table like the following:
CREATE TABLE parent_table(
id SERIAL PRIMARY KEY,
created_at TIMESTAMPTZ DEFAULT NOW(),
elements element_pk[]
)
And the Type like you described. If I were you, I wont implement it via an array. I would have use JSONB instead - in this way you wont lose an ability to use indexing. Of course, there is java.sql.Array out there, and you can still do it via the following:
Array array = connection.createArrayOf("public.element_pk", new ElementPK[]{
new ElementPK(1, 2, 3),
new ElementPK(9, 4, 6)
});
And then set the array, to PgPreparedStatement, but the thing is that internally PgPreparedStatement will enclose your elements within curly braces, which is ok, but each element will be represented by its toString method call result. I mean, assume your ElementPK toString looks like this:
#Override
public String toString() {
return "This is how it is implemented, really?";
}
then you will get the SQL like:
INSERT INTO parent_table (elements) VALUES ('{"This is how it is implemented, really?","This is how it is implemented, really?"}')
Again, it is maybe possible to adopt it, but from my perspective - at least having your logic within toString method is not that great, is it? Spare yourself and do the following:
CREATE TABLE via_jsonb(
elements JSONB
);
and then simply:
INSERT INTO via_jsonb VALUES(
'{
"workspace_id" : 1,
"element_id" : 2,
"history_id" : 3
}'
);
and in the Java code I would have simply create json from your object and set it as a string. Really, there are a lot of functions and cool features on JSONB out of the box.
Hope it helped, have a nice day!)
Follow up to the last part of #misha2045 answer.
If you still refuese to use JSONB for whatever reason you need to add parenthesis at the end and start of your toString method.
#Override
public String toString() {
return "("+this.field1+ ", " + this.field2+")";
}
Then set the array in the prepared statement
ps.setArray(1, con.createArrayOf("yourType", yourClassArray));

Set NULL value in jooq

I have an big query and my problem is setting NULL values using jooq.
For example, I have this piece of sql query:
IF(t.PANEL_ID IS NULL, t.ID, NULL) AS testId
If transform this into jooq implementation something like this will come out:
when(TEST.PANEL_ID.isNull(), TEST.ID).otherwise(null)).as("testId")
but this is ambiguous method call.
I made some research, and find this snippet:
DSL.val((String) null)
but it didn't work, because it cannot resolve method with jooq.Param<String>.
How should I proceed?
Your NULL expression must be of the same type as your TEST.ID column. I would imagine this is not a String column, but some numeric one. Irrespective of the actual data type, you can always create a bind value using the data type of another expression, e.g.
// Bind variable:
DSL.val(null, TEST.ID)
// Inline value / NULL literal
DSL.inline(null, TEST.ID)
If you're doing this a lot, you could also extract your own utility like this:
public static <T> util(Field<?> nullable, Field<T> other) {
return when(nullable.isNull(), other).otherwise(inline(null, other));
}
Notice, jOOQ has a built-in method NVL2 for this purpose:
nvl2(TEST.PANEL_ID, inline(null, TEST.ID), TEST.ID).as("testId")

How to pass null to SQL parameter in java?

I have a scenario where in i have to pass null to a SQL parameter through java but i am getting SQLGrammarException. If i am passing some value it works fine. Please guide if i am wrong somewhere.
Below is my code:
StringBuffer query;
query = new StringBuffer(
"SELECT * "+
"FROM table(package.func(travel_type => travel_search_type("+
"travel_place_no => :travelPlaceNo"+
")))" );
Query query1 = em.createNativeQuery(query.toString());
query1.setParameter("travelPlaceNo",null);
searchresults = query1.getResultList();
Exception:
org.hibernate.exception.SQLGrammarException
This is what i do through SQL Developer and it works fine:
SELECT *
FROM table(package.func(travel_type => travel_search_type(
travel_place_no => NULL))) ;
Please guide.
While calling the 2 argument signatures of the method, null is not an allowed value. You can use, instead, the 3 argument signature setParameter(String name,
Object val,
Type type) specifying the data type and it should work.
EDIT:
Ok, I see there is also another problem: even if the replacemente worked, what you are trying to execute is a >= NULL. In this case, I'm afraid that you are going to have to handle mannually the StringBuffer creation. Maybe just force an always false condition without parameters if its null, like `1!=2', and otherwise just handle it as you are doing on your sql example (write mannually 'NULL' instead of the parameter placeholder).

Implementing a Stored Procedure Call with Optional Parameters in Spring 3.0

I've been trying to find a way to make stored proc calls from Spring 3.0 to Oracle 11.2 with following the items in mind:
Many of the calls are made to stored procedures that have plethora of
optional parameters (parameters that have default values).
There are stored procedures that can have a mix of: IN, OUT, and/or IN OUT.
I do not need to handle the OUT's.
I would like to be able to call a stored proc with the needed parameters (required and/or optional). In other word, I do not wish to pass a value (not even a null) to optional parameters to my choosing (it seems like when null is being passed programmatically [not in PL/SQL though] to a parameter mapper, the default values don't get used). I have attempted to implement these invocations as many possible ways as I could but nothing has worked:
create or replace
procedure update_stored_proc (
h1 in boolean default false,
h2 in number,
h3 in varchar2 default 'H3',
h4 in varchar2 default 'H4',
h5 in varchar2 default 'H5',
h6 in out number
);
For update_stored_proc(), there is only two required parameters (h2 and h6), and four optional ones. My ultimate goal is to call update_stored_proc() by passing h1, h2 and h6. Even when I invoking the stored proc via SimpleJdbcCall with all the values set, I get an exception:
SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(updatingDataSource)
.withProcedureName("update_stored_proc")
.withoutProcedureColumnMetaDataAccess();
simpleJdbcCall.declareParameters(new SqlParameter("h1", OracleTypes.BOOLEAN))
.declareParameters(new SqlParameter("h2", OracleTypes.NUMBER))
.declareParameters(new SqlParameter("h3", OracleTypes.VARCHAR))
.declareParameters(new SqlParameter("h4", OracleTypes.VARCHAR))
.declareParameters(new SqlParameter("h5", OracleTypes.VARCHAR))
.declareParameters(new SqlInOutParameter("h6", OracleTypes.NUMBER));
MapSqlParameterSource in = new MapSqlParameterSource()
.addValue("h1", false, OracleTypes.BOOLEAN)
.addValue("h2", 123, OracleTypes.NUMBER)
.addValue("h3", "h3", OracleTypes.VARCHAR)
.addValue("h4", "h4", OracleTypes.VARCHAR)
.addValue("h5", "h5", OracleTypes.VARCHAR)
.addValue("h6", "h6", OracleTypes.NUMBER);
simpleJdbcCall.compile();
simpleJdbcCall.execute(in);
The exception I get indicates that the column type is somehow invalid:
org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback; uncategorized SQLException for SQL [{call update_stored_proc(?, ?, ?, ?, ?, ?)}]; SQL state [99999]; error code [17004]; Invalid column type; nested exception is java.sql.SQLException: Invalid column type
I have replaced OracleTypes with Types and even taken out withoutProcedureColumnMetaDataAccess() but the error persists.
This question turned out to require several solutions to get to work. First and foremost, Oracle's implementation of SQL standards does not contain BOOLEAN type, even though its propriety PL/SQL script language does support it (more reason for the DBA's not to use BOOLEAN type in their stored procedures). The solution I came up with was to use an anonymous block, declare a local variable, and assign it to the BOOLEAN parameter:
DECLARE
h1_false BOOLEAN := false;
h6_ NUMBER;
BEGIN
update_stored_proc(
h1 => h1_false,
h2 => :h2,
h6 => h6_
);
END;
Note that I do not care about the value of h1 (and for that matter, the OUT parameter/h6) parameter for my particular functionality, but I could imagine by having a simple IF THEN ELSE statement, one can assign a binded parameter to h1, i.e.
DECLARE
h1_false BOOLEAN;
h6_ NUMBER;
BEGIN
IF :h1 THEN
h1_false = true;
ELSE
h1_false = false;
END IF;
update_stored_proc(
h1 => h1_false,
h2 => :h2,
h6 => h6_
);
END;
The second issue is the way Spring handles the optional parameters. If one to declare the optional parameters, then Spring's SimpleJdbcCall -- and StoredProcedure for that matter -- expects values for those parameters. Therefore, one must take care of these values when they become available. Otherwise, when the optional parameter does not have a value, you must pass NULL which will not trigger the DEFAULT value of the PL/SQL's parameter to be used. This means that your query string (in this case an anonymous PL/SQL block) must be generated dynamically. So, the anonymous block becomes:
DECLARE
h1_false BOOLEAN := false;
h6_ NUMBER;
BEGIN
update_stored_proc(
h1 => h1_false,
h2 => :h2,
h6 => h6_
I switched to StoredProcedure solution rather than SimpleJdbcCall, as it turned out to be more simpler. Albeit I must add that I had to extend StoredProcedure to create a common class for my stored procedure classes and use that extended class for my customized stored procedure classes. In your StoredProcedure class, you only declare the required parameters (make sure not to compile() the query at this point):
declareParameter(new SqlParameter("h2", OracleTypes.NUMBER));
Note: If you do not need the OUT parameter, like in my case, do not include it in your declaration. Otherwise, assign a binding variable, i.e. h6 => :h6 in the anonymous block and declare it in your StoredProcedure, i.e. declareParameter(new SqlOutParameter("h6", OracleTypes.NUMBER)); and make sure to get :h6 value out when execute() returns a Map<String, Object>. If your OUT value is of BOOLEAN type, then I do not know how to retrieve the value.
The remaining optional parameters should be dynamically constructed in your StoredProcedure. i.e.:
if (someObj.getH3() != null) {
declareParameter(new SqlParameter("h3", OracleTypes.VARCHAR));
paramMap.put("h3", someObj.getH3());
anonymousPLSQLBlockQueryString += " , h3 => :h3";
}
where paramMap represents a Map that is going to be passed to StoredProcedure#execute(paramMap). You do the same with h4 and h5, and at the end, you have to make sure to properly close the anonymous block query string, i.e.:
anonymousPLSQLBlockQueryString += " ); END;";
setSql(anonymousPLSQLBlockQueryString);
setSqlReadyForUse(true);
compile();

how to use Oracle's regexp_like in Hibernate HQL?

I am using oracle 10g and hibernate 3.3.2. I have used regular expression in sql before, now for the first time I am using it in HQL.
Query query = getSession().createQuery("From Company company
where company.id!=:companyId and
regexp_like(upper(rtrim(ltrim(company.num))), '^0*514619915$' )");
This is my hql, when i run it without regex_like function it runs as expected. But I am not able to execute it with regex_like expression.
It says..
nested exception is org.hibernate.hql.ast.QuerySyntaxException:
unexpected AST node: ( near line 1, column 66.....
Kindly help, how can I use regex_like in hibernate native query? OR some other alternative to do so.
Actually, you can't compare the result of REGEXP_LIKE to anything except in conditional statements in PL/SQL.
Hibernate seems to not accept a custom function without a returnType, as you always need to compare the output to something, i.e:
REGEXP_LIKE('bananas', 'a', 'i') = 1
As Oracle doesn't allow you to compare this function's result to nothing, I came up with a solution using case condition:
public class Oracle10gExtendedDialect extends Oracle10gDialect {
public Oracle10gExtendedDialect() {
super();
registerFunction(
"regexp_like", new SQLFunctionTemplate(StandardBasicTypes.BOOLEAN,
"(case when (regexp_like(?1, ?2, ?3)) then 1 else 0 end)")
);
}
}
And your HQL should look like this:
REGEXP_LIKE('bananas', 'a', 'i') = 1
It will work :)
You can most definitely use any type of database-specific function you wish with Hibernate HQL (and JPQL as long as Hibernate is the provider). You simply have to tell Hibernate about those functions. In 3.3 the only option for that is to provide a custom Dialect and register the function from the Dialect's constructor. If you take a look at the base Dialect class you will see lots of examples of registering functions. Usually best to extend the exact Dialect you currently use and simply provide your extensions (here, registering the function).
An interesting note is that Oracle does not classify regexp_like as a function. They classify it as a condition/predicate. I think this is mostly because Oracle SQL does not define a BOOLEAN datatype, even though their PL/SQL does and I would bet regexp_like is defined as a PL/SQL function returning BOOLEAN...
Assuming you currently use Oracle10gDialect, you would do:
public class MyOracle10gDialect extends Oracle10gDialect {
public Oracle10gDialect() {
super();
registerFunction(
"regexp_like",
new StandardSQLFunction( "regexp_like", StandardBasicTypes.BOOLEAN )
);
}
}
I cannot remember if the HQL parser likes functions returning booleans however in terms of being a predicate all by itself. You may instead have to convert true/false to something else and check against that return:
public class MyOracle10gDialect extends Oracle10gDialect {
public Oracle10gDialect() {
super();
registerFunction(
"regexp_like",
new StandardSQLFunction( "regexp_like", StandardBasicTypes.INTEGER ) {
#Override
public String render(
Type firstArgumentType,
List arguments,
SessionFactoryImplementor factory) {
return "some_conversion_from_boolean_to_int(" +
super.render( firstArgumentType, arguments, factory ) +
")";
}
}
);
}
}
You can't access specific database functions unless JPAQL/HQL provide a way to do so, and neither provide anything for regular expressions. So you'll need to write a native SQL query to use regexes.
On another, and very important point, a few colleagues (Oracle DBAs) told me to never use regexes in oracle, as they can't be indexed, which ends up in the DB performing a full DB scan. If the table has a few entries, then it's ok, but if it has lots of rows, it might cripple the performance.
For those using Hibernate criterion with sqlRestriction (Hibernate Version 4.2.7)
Criterion someCriterion = Restrictions.sqlRestriction("regexp_like (column_name, ?, 'i')", "(^|\\s)"+searchValue+"($|\\s|.$)", StringType.INSTANCE);
Or another option is to create similar function in oracle which will return numeric value based on operation result. Something like that
CREATE OR REPLACE FUNCTION MY_REGEXP_LIKE(text VARCHAR2, pattern VARCHAR2)
RETURN NUMBER
IS function_result NUMBER;
BEGIN
function_result := CASE WHEN REGEXP_LIKE(text, pattern)
THEN 1
ELSE 0
END;
RETURN(function_result);
END MY_REGEXP_LIKE;
and you will be able to use
MY_REGEXP_LIKE('bananas', 'a') = 1
You can use Specification.
Specification<YourEntity> specification = (root, query, builder) -> builder.equal(builder.selectCase()
.when(builder.function("regexp_like", Boolean.class, root.get("your_field"), builder.literal("^0*514619915$")), 1)
.otherwise(0), 1);
List<YourEntity> yourEntities = yourEntityRepository.findAll(specification);
i found the solution Accessing REGEXP_LIKE function in CriteriaBuilder useful for this. Add the dialect based on your Oracle version.

Categories

Resources