Hibernate and Postgres : function st_buffer(bytea, numeric) is not unique - java

I'm working with Hibernate 5.1.0 in a Java application. I'm connecting both to a Postgres 9.5 with Postgis extensions and Oracle databases.
I need to find all the geometries in my database that intersect with a given geometry to which I apply a buffer, such as :
Query query = session
.createQuery("select b from Block b where intersects(b.geom, buffer(:geometry, " + bufferDistance + ")) = "
+ UtilsHelper.getTrueBooleanValue(em));
query.setParameter("geometry", geom);
List<Block> blocks = query.list();
That works in Oracle, but in Postgres I will get the error :
Caused by: org.postgresql.util.PSQLException: ERROR: function st_buffer(bytea, numeric) is not unique
Hint: Could not choose a best candidate function. You might need to add explicit type casts.
Position: 243
at org.postgresql.core.v3.QueryExecutorImpl.receiveErrorResponse(QueryExecutorImpl.java:2270)
This makes sense, as it will not be able to choose between one of the following functions :
geometry ST_Buffer(geometry g1, float radius_of_buffer);
geography ST_Buffer(geography g1, float radius_of_buffer_in_meters);
The UtilsHelper.getTrueBooleanValue(em) will just get the right boolean value depending on the entity manager, that is, 0/1 for Oracle and true/false for Postgres.
One obvious solution would be to drop one of the functions. Other than that, is there any way I can fix this?

I won't claim to know much about Hibernate, but it seems that a simple fix is to explicitly cast the bytea with CAST(:geometry AS geometry), and modernize the rest query to add a "ST_" prefix, which is used with newer PostGIS versions.
More importantly, you should never write a query with the form:
SELECT b
FROM Block b
WHERE ST_Intersects(b.geom, ST_Buffer(CAST(:geometry AS geometry), :bufferDistance)) = TRUE;
Using a buffer to select a region is slower and imperfect than using a distance-based ST_DWithin function to find the geometries that are within a distance. ST_DWithin can also use a spatial index, if available. And a boolean operator does not need to have the = + UtilsHelper.getTrueBooleanValue(em) part (i.e. TRUE = TRUE is TRUE). Just remove it.
Try to write a function that looks more like:
SELECT b
FROM Block b
WHERE ST_Dwithin(b.geom, CAST(:geometry AS geometry), :distance);
(using two parameters :geometry and :distance)

Related

H2 DB mapping to IBM DB2 database UDF for missing function: STRIP

I am trying to setup a unit test environment for a large codebase. For these unit tests I use H2 database instead of the IBM DB2 database, which is used in production and I already implemented a few UDF's to map IBM DB2 related functions to the H2 database.
Some more Details about the project:
Java 8 JDK 321 64 Bit
DB2 12, DSN 12015
H2 version 2.1.212 with <MODE=DB2;DEFAULT_NULL_ORDERING=HIGH;
I am trying to implement the IBM Db2 function: strip() Reference Doc - IBM. This function is called as part of a larger select statement with the clause: STRIP(T2.ITEM_TYPE_NAME_GER, B, ' '). While I can map the first and last input parameter to a Java function and call this function as an ALIAS in H2, I was not able to manage to get the 2nd parameter interpreted in the correct way as a String or Expression. The JDBC/H2 engine always tries to map it to a table column:
org.h2.jdbc.JdbcSQLSyntaxErrorException: Feld "B" nicht gefunden
Column "B" not found; SQL statement:
SELECT T1.ITEM_TYPE_KEY,T1.SUPER_ITM_TYPE_KEY,T2.ITM_TYPE_KEY_TRANS,T2.ITEM_TYPE_NAME,T2.COMPLEX_FLAG,T2.ITEM_CATEGORY,T2.HEADER_FLAG,T2.HEADER_NO ,T2.LEVEL_NO,strip(T2.ITEM_TYPE_NAME_GER, B, ' ') ,T2.LEVEL1_DISPLAY FROM public.AA752T T1, public.AA743T T2 WHERE T1.ITEM_TYPE_KEY NOT IN ('F4CO', 'F4CB', 'F4RB', 'F4SO', 'F4SB', 'F4RO') AND T1.ITEM_TYPE_KEY = T2.ITEM_TYPE_KEY ORDER BY T2.HEADER_NO,T2.HEADER_FLAG DESC,T2.LEVEL_NO,T1.SUPER_ITM_TYPE_KEY,T2.LEVEL_PRIORITY [42122-212]
at org.h2.message.DbException.getJdbcSQLException(DbException.java:502)
at org.h2.message.DbException.getJdbcSQLException(DbException.java:477)
at org.h2.message.DbException.get(DbException.java:223)
at org.h2.message.DbException.get(DbException.java:199)
at org.h2.expression.ExpressionColumn.getColumnException(ExpressionColumn.java:244)
at org.h2.expression.ExpressionColumn.optimizeOther(ExpressionColumn.java:226)
at org.h2.expression.ExpressionColumn.optimize(ExpressionColumn.java:213)
at org.h2.expression.function.JavaFunction.optimize(JavaFunction.java:59)
at org.h2.command.query.Select.prepareExpressions(Select.java:1170)
at org.h2.command.query.Query.prepare(Query.java:218)
at org.h2.command.Parser.prepareCommand(Parser.java:574)
at org.h2.engine.SessionLocal.prepareLocal(SessionLocal.java:631)
at org.h2.engine.SessionLocal.prepareCommand(SessionLocal.java:554)
at org.h2.jdbc.JdbcConnection.prepareCommand(JdbcConnection.java:1116)
at org.h2.jdbc.JdbcPreparedStatement.(JdbcPreparedStatement.java:92)
at org.h2.jdbc.JdbcConnection.prepareStatement(JdbcConnection.java:288)
at com.db.cib.gbd.gps.pbs.pricing.StaticItemDetails.retriveItemDisplayDetails(StaticItemDetails.java:920)
This is my Java UDF:
public static String strip(String s, Expression loc, String trimConstant) {
if (loc.toLowerCase() == "b" || loc.toLowerCase() == "both") {
s = s.replaceAll("^[" + trimConstant + "]+|[ \t]+$", "");
} else if (loc.toLowerCase() == "l" || loc.toLowerCase() == "leading") {
s = s.replaceAll("^[" + trimConstant + "]+", "");
} else if (loc.toLowerCase() == "t" || loc.toLowerCase() == "trailing") {
s = s.replaceAll("[" + trimConstant + "]+$", "");
}
return s;
}
Is there a possibility to get the mapping of the column in the correct way, or can you suggest either a SQL function, which is usable as UDF alias (how is this usable?) or a way to solve this error?
To avoid this question: I cannot change the existing sql statement. I have to find an alias for this function.
It isn't possible to create a user-defined function with special arguments in H2 and most likely in all or almost all other database systems. User-defined functions accept only plain comma-separated arguments with literals or expressions in them.
(You also cannot declare a parameter as org.h2.expression.Expression.)
The proper solution here is to use the TRIM function from the SQL Standard:
https://h2database.com/html/functions.html#trim
https://www.ibm.com/docs/en/db2-for-zos/11?topic=functions-trim
TRIM(BOTH ' ' FROM T2.ITEM_TYPE_NAME_GER)
Please note that B, L, and T acronyms is a DB2-specific extension, in H2 you can use only standard BOTH, LEADING, and TRAILING.
If you cannot change your query you can only modify sources of H2 and compile its own version with support of the STRIP. But actually you can run into some other issue immediately. When you want to use multiple database systems at once you need to realize that they all are very different from each other. H2 provides compatibility modes for others, but even in these modes there is a very limited compatibility. It means you need to avoid usage of vendor-specific functions and other grammar elements and in some cases different SQL for different systems may be required.
You also can try to create constants B, BOTH, L, LEADING, T, and TRAILING with some values
CREATE CONSTANT B VALUE 1;
CREATE CONSTANT BOTH VALUE 1;
CREATE CONSTANT L VALUE 2;
…
and create a function with three arguments, second argument will be of type int (or other, if you'll decide to choose values of some other data type). But names of constants may conflict with column names, so this workaround is far from being perfect and may not work at all in some queries.

JDBC with H2 and MySQL mode: create procedure fails

Description of code
Database connection
I try to to store Java object locally database without use external database.
For this I use JDBC with H2 via Hibernate :
/**
* #param connection the connection to set
*/
public static void setConnectionHibernate() {
Properties connectionProps = new Properties();
connectionProps.put("user", "sa");
try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
url = "jdbc:h2:mem:db1;DB_CLOSE_DELAY=-1;MODE=MySQL;";
}
Query
I store the PROCEDURE in String with this code :
static final String CREATE_PROCEDURE_INITPSEUDOS = "CREATE OR REPLACE PROCEDURE init_pseudos (MaxPseudo INT) BEGIN WHILE MaxPseudo >= 0 DO"
+
" INSERT INTO Pseudos (indexPseudo)" +
" VALUES (MaxPseudo);" +
" SET MaxPseudo = MaxPseudo - 1;" +
" END WHILE;" +
" END init_pseudos;";
Query execution
And I execute the statement with this code :
public static void initBaseDonneePseudos() {
try (Connection connection = DriverManager.getConnection(url, connectionProps);
Statement stmt = connection.createStatement()) {
stmt.execute(RequetesSQL.CREATE_TABLE_PSEUDOS);
stmt.execute(RequetesSQL.CREATE_PROCEDURE_INITPSEUDOS);
stmt.execute(RequetesSQL.CREATE_FUNCTION_RECUPEREPSEUDO);
stmt.execute(RequetesSQL.INIT_TABLE_PSEUDOS);
} catch (SQLException e) {
e.printStackTrace();
}
}
Problem
Test
I execute this test to test statement :
#Nested
class BaseDonneeInteractionTest {
#BeforeEach
public void setUp() {
BaseDonnee.setConnectionHibernate();
}
#Test
void testInitBaseDonnee() {
assertDoesNotThrow(() -> BaseDonnee.initBaseDonneePseudos());
}
}
Error
But I obtain this error
I didn't find the problem of the query, anybody have the solution to solve this ?
The "MySQL Compatibility Mode" doesn't make H2 100% compatible with MySQL. It just changes a few things. The documentation lists them:
Creating indexes in the CREATE TABLE statement is allowed using INDEX(..) or KEY(..). Example: create table test(id int primary key, name varchar(255), key idx_name(name));
When converting a floating point number to an integer, the fractional digits are not truncated, but the value is rounded.
ON DUPLICATE KEY UPDATE is supported in INSERT statements, due to this feature VALUES has special non-standard meaning is some contexts.
INSERT IGNORE is partially supported and may be used to skip rows with duplicate keys if ON DUPLICATE KEY UPDATE is not specified.
REPLACE INTO is partially supported.
Spaces are trimmed from the right side of CHAR values.
REGEXP_REPLACE() uses \ for back-references.
Datetime value functions return the same value within a command.
0x literals are parsed as binary string literals.
Unrelated expressions in ORDER BY clause of DISTINCT queries are allowed.
Some MySQL-specific ALTER TABLE commands are partially supported.
TRUNCATE TABLE restarts next values of generated columns.
If value of an identity column was manually specified, its sequence is updated to generate values after inserted.
NULL value works like DEFAULT value is assignments to identity columns.
Referential constraints don't require an existing primary key or unique constraint on referenced columns and create a unique constraint automatically if such constraint doesn't exist.
LIMIT / OFFSET clauses are supported.
AUTO_INCREMENT clause can be used.
YEAR data type is treated like SMALLINT data type.
GROUP BY clause can contain 1-based positions of expressions from the SELECT list.
Unsafe comparison operators between numeric and boolean values are allowed.
That's all. There is nothing about procedures. As #jccampanero pointed out in the other answer, you must use the syntax specific to H2 if you want to create stored procedures.
The problem is that in H2 there are not explicit procedures or functions as you are trying defining.
For that purpose, H2 allows you to create used defined functions instead. Please, consider reed the appropriate documentation.
Basically, you create a user defined function by declaring an ALIAS for a bunch of Java code.
For example, in your use case, your CREATE_PROCEDURE_INITPSEUDOS could look similar to this:
CREATE ALIAS INIT_PSEUDOS AS $$
import java.sql.Connection;
import java.sql.Statement;
import java.sql.SQLException;
#CODE
void init_pseudos(final Connection conn, final int maxPseudo) throws SQLException {
try (Statement stmt = conn.createStatement()) {
while (maxPseudo >= 0) do {
stmt.execute("INSERT INTO Pseudos (indexPseudo) VALUES (MaxPseudo);");
maxPseudo = maxPseudo - 1;
}
}
}
$$;
Note the following:
As I said, you define a user defined function as Java code. That Java code should be enclosed between two $$ delimiters.
Although I included explicitly some imports, you can use any class in the java.util or java.sql packages in your code. If you want to included explicitly some imports, or if you require classes from other packages than the mentioned, the corresponding imports should be provided right after the first $$ token. In addition, you need to include #CODE the signal H2 where your imports end and your actual Java method starts.
If you need a reference to a Connection to the database in your code, it should be the first argument of your method.
Prefer to raise and not hide exceptions: it will allow your transactions to be committed or rollbacked as a whole appropriately.
You can invoke such a function as usual:
CALL INIT_PSEUDOS (5);
Please, provide the appropriate value for the maxPseudo argument.
Please, consider the provided code as just an example of use: you can improve the code in different ways, like using PreparedStatements instead of Statements for efficiency purposes, checking parameters for nullability, etcetera.
Using H2 as a test database product
Historically, H2 has been used a lot as a test replacement for the actual production database product. But this means you're limiting yourself to the least common denominator between H2 and MySQL, in your case. Including, as others pointed out, the lack of support for stored procedures. You could re-implement all the stored procedures in H2 (very tedious and error prone), or, you could just use testcontainers and run actual integration tests on MySQL directly.
I really think that using H2 as an integration test database is an outdated concept as I've shown in this blog post (unless you're also using H2 in production). You'll be much happier developing and testing everything against MySQL directly!
Using recursive SQL instead
You don't really need that procedure, I think? You probably wrote it to avoid too many round trips to the database for that loop. But you could batch the inserts or generate the following recursive SQL:
INSERT INTO Pseudos (indexPseudo)
WITH RECURSIVE
p (i) AS (
SELECT MaxPseudo
UNION ALL
SELECT i - 1 FROM p WHERE i >= 0
)
SELECT i FROM p;
Now there's no more need to place this in a procedure, you can run the query directly.
Translating your procedure to H2
Just to be complete, for translation of simple procedural logic, jOOQ would offer the feature transparently. You can try it online, here. It's probably overkill, I recommend the other approaches, but perhaps worth a try.
Disclaimer: I work for the company behind jOOQ.

Spring(Boot) with JPA / Hibernate and PostgreSQL - use Overlaps for dates

So i have a project where we use springBoot and PostgreSQL 10 with PostGis and hibernate.spatial for spatial queries. Everything works fine so far.
A new requirement is to find entities, which start-end dates overlap the start-end dates of the query in any possible way (ranges might be enclosing, start-overlap, inbetween, end-overlap).
In PostgreSQL theres the Overlaps operator that seems a pretty good fit for the job.
When trying to use it within my JPA-Query for an Entity "Sth" like this..
select sth from Sth sth where 1=1 and (sth.start, sth.end) overlaps (:begin, :end)
// set begin and end params..
i get one of..
antlr.NoViableAltException: unexpected token: overlaps
antlr.NoViableAltException: unexpected AST node: (
org.postgresql.util.PSQLException: FEHLER: rt_raster_from_wkb: wkb size (5) < min size (61)
Is it possible to use overlaps for dates with JPA without writing a native query?
So seems like there are three things you need to do to make it work.
Its probably not possible to use overlaps as an operator, but luckily it seems it can also be used as a function: overlaps(start1, end1, start2, end2)
Overlaps is not mapped by any of the hibernate-core PostgreSQL[NN]Dialects. But it is mapped by hibernate-spatial PostgisPG[NN]Dialects, where it maps to the st_overlaps-function for spatial overlapping. So you need to use your own custom Dialect that registers the overlaps function with an alias like so:
public class PostgisDialect extends org.hibernate.spatial.dialect.postgis.PostgisPG95Dialect {
public PostgisDialect() {
super();
registerFunction("dateoverlaps", new StandardSQLFunction("overlaps", StandardBasicTypes.BOOLEAN));
}
}
and specify it as your spring.jpa.properties.hibernate.dialect (or spring.jpa.database-platform 🤷‍♂️).
Your JPA query must include a = true like this:
select sth from Sth sth where 1=1 and dateoverlaps(sth.start, sth.end, :begin, :end) = true
You might addtionally enhance this by using the coalesce function to handle null-values, i.e.
select sth from Sth sth where 1=1 and dateoverlaps(coalesce(sth.start, sth.plannedStart), coalesce(sth.end, '3999-01-01'), :begin, :end) = true
which uses plannedStart when start is null and a long-in-the-future-date when end is null / open.

How do I query OrientDB Vertex graph object by Record ID in Java?

How do I retrieve an OrientDB Document/Object or Graph object using its Record ID? (Language: Java)
I'm referring to http://orientdb.com/docs/2.0/orientdb.wiki/Tutorial-Record-ID.html and Vertex.getId() / Edge.getId() methods.
It is just like an SQL query "SELECT * from aTable WHERE ID = 1".
Usage/purpose description: I want to store the generated ID after it is created by OrientDB, and later retrieve the same object using the same ID.
(1) I'd suggest using OrientDB 2.1, and its documentation, e.g. http://orientdb.com/docs/2.1/Tutorial-Record-ID.html
(2) From your post, it's unclear to me whether you need help obtaining the RID from the results of a query, or retrieving an object given its RID, so let me begin by mentioning that the former can be accomplished as illustrated by this example (in the case of an INSERT query):
ODocument result=db.command(new OCommandSQL(<INSERTQUERY>)).execute();
System.out.println(result.field("#rid"));
Going the other way around, there are several approaches. I have verified that the following does work using Version 2.1.8:
OrientGraph graph = new OrientGraph("plocal:PATH_TO_DB", "admin", "admin");
Vertex v = graph.getVertex("#16:0");
An alternative and more generic approach is to construct and execute a SELECT query of the form SELECT FROM :RID, along the lines of this example:
List<ODocument> results = db.query(new OSQLSynchQuery<ODocument>("select from " + rid));
for (ODocument aDoc : results) {
System.out.println(aDoc.field("name"));
}
(3) In practice, it will usually be better to use some other "handle" on OrientDB vertices and edges in Java code, or indeed when using any of the supported programming languages. For example, once one has a vertex as a Java Vertex, as in the "Vertex v" example above, one can usually use it.

Issue at like operation for numbers in oracle

This is a project on java with openjpa ORM. I have an issue with like selection for type NUMBER(10,2) in Oracle database.
The type of number in JPA is BigDecimal and numbers could be like integer and not integer, for example:
123456
or 123456,01; 123456,15
At presentation layer user has global filter, but not only for this column, where he can choose column and operator (like, equal or between) and define value.
For examaple: column1 like '%56%'
Also numbers at presentation layer has presision 2, so:
123456,00 ->'123456,00'
123456,01 -> '123456,01'
When Oracle database executes like opearaion, nuber is converted ot String type:
123456,00 -> '123456'
and 123456,01 -> '123456,01'
So the result of operation column1 like '%,0%' is only
123456,01 but not both.
I also tried call native SQL function like here but we use OpenJPA 1.2.3 with JPA 1.0 implementation.
Please help me find workaround at my issue.
I don't know if this helps you but on Oracle side you should use:
select *
from your_table
where to_char(your_column,'99999999.99') like '%.0%';

Categories