Comparison issue with boolean mapped as BIT(1) for MySQL and H2 2.x - h2

We are facing the following error when trying to upgrade H2 from version 1.4.200 to 2.1.214.
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Values of types "ROW(C1 BOOLEAN, C2 BIGINT)" and "ROW(C1 INTEGER, C2 INTEGER)" are not comparable;
Relevant SQL part from account this_ where (this_.admin, this_.id) >= (0, 5) order by this_.admin asc, this_.id asc limit ?
Please note that the parameters 0 and 5 (and actually the whole WHERE, before the "limit" part) are being added as a String using criteria.addQueryHint() (I know, it is a corner case, to add paging without offset)
I've read a lot of discussions about similar issues, but none of the solutions/workarounds seem applicable to our case.
We're using H2 just for automated tests, in production we use MySQL.
BIT is the default column type Hibernate maps for boolean in MySQL:
MySQLDialect in Hibernate 5.6.10.Final has:
registerColumnType( Types.BOOLEAN, "bit" ); // HHH-6935
I have tried adding the following code prior to executing the statement, but it didn't help.
Mode mode = Mode.getInstance("MySQL"); mode.numericWithBooleanComparison = true;
I have also tried to add registerColumnType(Types.BOOLEAN, "bit"); and registerColumnType(Types.BOOLEAN, "bit(1)"); to a H2CustomDialect and use it instead of H2Dialect, none of them helped.
Changing column types in production for several tables, each one containing hundreds of millions of records is not something I'm considering here.
What should be my approach to this problem? I don't want to stay in version 1.4.200 forever.

New versions of H2 don't allow any comparison operations between BOOLEAN and numeric data types in the Regular compatibility mode without explicit casts to comparable data types.
In MySQL and some other compatibility modes you can compare BOOLEAN and numeric values for equality (=) or not equality (<>), but you still cannot compare them with >, >=, <, and <= operators. What exactly do you expect for something like TRUE >= 2? Does it mean 1 >=2 (FALSE) or TRUE >= TRUE (TRUE)?
Because you add >= (0, 5) part by yourself, you can simply add >= (FALSE, 5) instead for both H2 and MySQL. MySQL doesn't have boolean data type, but it knows standard TRUE and FALSE literals.

Related

Migration to SpringBoot 3: trunc a date with oracle driver no longer works (hibernate)

I am currently migrating a SpringBoot 2.7 application to SpringBoot 3. The following query is used within a SpringData repository:
#Query("select b from #{#entityName} b where (trunc(b.date) <= trunc(:date))")
List<T> findByDate(LocalDateTime date);
While this works great in SpringBoot 2.7, with SpringBoot 3 the following message is thrown:
org.hibernate.QueryException: Parameter 1 of function trunc() has type NUMERIC, but argument is of type java.time.LocalDateTime
Unfortunately, a simple migration to datetrunc was unsuccessful:
Error Msg = ORA-00904: "DATETRUNC": ungültige ID
Does anyone have a solution for this?
Best regards
So, as I noted in my previous answer, there's no standard HQL function for date truncation. That's because it would be quite difficult to implement in most SQL dialects (I have not really investigated to see quite how difficult, but it's at least nontrivial.)
However, just especially for you, in Hibernate 6.2, which should go into CR today, what I have done is added undocumented (but tested) support for the date_trunc() function under Postgres, DB2, Oracle, and H2. On Oracle, this translates to trunc(), of course.
For example, you could write:
date_trunc(year,current_timestamp)
When I say it's "undocumented" I mean you use it at your own risk. (The documented way to do this remains to use a FunctionContributor.)
I hope that helps.
UPDATE:
Oh and by the way, I just noticed that you're not actually using the full form of the trunc() function in your query. You're actually just stripping off the time part of a timestamp.
There's actually multiple ways to do that in HQL without needing to use the Oracle-specific trunc() function. (But of course trunc()/date_trunc() can do more things that you're not using here.)
JPA-standard way
One of the improvements I added to the new JPA 3.1 specification was to let you write:
extract(date from current_timestamp)
To strip off the time part of the timestamp.
HQL alternative
But you can also do it using the HQL cast() function if you prefer:
cast(current_timestamp as Date)
These two options translate to the same SQL on Oracle.
In Hibernate 6 we started checking the argument types of HQL functions.
Since trunc(numeric,places) is a very common function across many dialects of SQL, we register it as one of the known functions, though we have not yet promoted it to a “standard” HQL function (perhaps we should).
On the other hand, Oracle’s trunc(date) is extremely specific to Oracle and is more like the date_trunc() function on DB2 and Postgres. We have not (so far) attempted to standardize on any sort of function for timestamp truncation, and I don’t consider it a high priority. This function is not registered by OracleDialect.
So, therefore, if you try to call trunc() on a Date, you will get a typing error.
Now, in general, in Hibernate 6.x:
We do not guarantee that Hibernate Dialects know about every single SQL function in your database, and you can't depend on that.
Instead we have a rather long list of documented HQL functions that we promise work portably across every database we support. (Right now trunc() is not on that list, but it almost made it onto the list.)
And that's perfectly fine, because we also provide APIs that make it very easy to register new SQL functions, either platform-specific functions like this one, or functions you've written yourself, either by:
writing a custom Dialect, or
providing a FunctionContributor.
https://docs.jboss.org/hibernate/orm/6.1/userguide/html_single/Hibernate_User_Guide.html#hql-user-defined-functions
Alternatively, if that's too much work, you can just use the JPA-standard syntax for calling native SQL functions, which is function('trunc', date, 'YEAR'), but I consider this a bit unlovely.
I'm using Oracle database and after migration to new hibernate I had the same issue.
I solved it by overriding org.hibernate.dialect.OracleDialect and adding ORACLE_TRUNC function to a functionRegistry. I've used the same definition trunc function definition as in Hibernate < 6 Oracle8iDialect.
public class CustomOracleDialect extends OracleDialect {
public CustomOracleDialect() {
super();
}
#Override
public void initializeFunctionRegistry(FunctionContributions functionContributions) {
super.initializeFunctionRegistry(functionContributions);
functionContributions.getFunctionRegistry().register("ORACLE_TRUNC", new StandardSQLFunction("trunc"));
}
}
Configure hibernate to use this dialect inapplication.properties / application.yml file:
spring:
jpa:
database-platform: com.example.CustomOracleDialect
In your queries use ORACLE_TRUNC() instead of TRUNC().
I tried using DATE_TRUNC(date, my_date) function from hibernate 6.2 RC2 as Gavin King said - but it transformed my query to CAST(my_date AS DATE) instead of TRUNC(my_date) causing date comparison issues.

Filtering query with R2DBC

I need do a filtering query with some query parameter. I need that if the query parameter is null, the condition of the query (= or LIKE, for example) is not evaluated and return me everything. I'm using R2DBC and I don't find a way to solve it.
If you are using Spring Data R2dbc, besides the above raw SQL query, you can use R2dbcOperations to compose Criteria by condition freely.
The following is an example.
template
.select(Post.class)
.matching(
Query
.query(where("title").like("%" + name + "%"))// extract where here you can assemble the query condition freely.
.limit(10)
.offset(0)
)
.all();
Additionally using R2dbcRepository and convention-based method, try to use a default value in the like(eg. set the null to empty string "" in like) to save work to determine if it is a null value in sql.
A general prepared statement which would work might be:
SELECT *
FROM yourTable
WHERE col = ? or ? IS NULL;
In the event that you bind a NULL value to the ? from your obfuscation layer, the WHERE clause would always be true, returning all records in the table.
If you prefer doing this with a "static SQL statement" (meaning you use a single SQL string for all possible bind values), then, in Oracle, it's probably optimal to use NVL() to profit from an Oracle optimiser feature, as explained in this article, irrespective of whether you're using R2DBC:
SELECT *
FROM t
WHERE col = nvl(:bind, col)
However, a query like yours is often best implemented using dynamic SQL, such as supported by a third party library like jOOQ:
Flux<TRecord> result =
Flux.from(ctx
.selectFrom(T)
.where(bind == null ? noCondition() : T.COL.eq(bind))
);
You can obviously also do this yourself, directly with R2DBC and your own dynamic SQL library, or any other such library.
Disclaimer: I work for the company behind jOOQ.

Delphi ADO - how to set default dataset column type without designer

Using Delphi 2007.
We do not use the designer to configure our DataSet. It is built automatically from the SQL query. So we need a solution where the fields are built at runtime.
I need to SELECT a large number (e.g. 2305843009213693951) from a NUMBER(19,0) column in Oracle using ADO. Our query works in SQLServer where the column is defined as BIGINT and is mapped automatically to a TLargeintField but for Oracle it is mapped to TBCDField. This behaviour is documented in a couple of places for instance here - TADOQuery.EnableBCD.
The problem (as mentioned in the same page) is that our number is in some instances too large and an overflow exception is thrown (it uses decimal under the hood). As expected / documented - if I use TADOQuery.EnableBCD(false) then the column is mapped to a TLargeintField which is what I want. However this option is set for all columns and seems a bit heavy handed.
An alternative is described:
Note: For fields with very large numbers of more than 19 significant digits, you can use TVariantField type persistent field objects. The TFloatField and TBCDField classes lack sufficient capacity for fields of this size. TVariantField allows the getting and setting of the field data as strings, preventing without loss of data due to insufficient capacity. However, arithmetic operations cannot be performed on numbers accessed through a TVariantField object.
So I tried configuring the fields to columns manually as described in this article - Creating a ClientDataSet's Structure at Runtime using TFields. But this doesn't appear to work as I get the exception: "Type mismatch for field 'total_rec' expecting: LargeInt actual: BCD'.
So is there a way to force ADO to return the dataset with a field of type TLargeintField for a NUMBER(19,0) without using the designer?
Interestingly ODAC maps the column to TLargeintField, but we are supposed to support ODAC and ADO.
I don't believe there is a way to manually control column data types returned by the ADOQuery. I tested overriding the procedure mentioned by kobik which determines the data type and this worked well. However we are not going to use this solution because I looked at the same code in XE4 and it appears it has been modified for use with large BCD numbers. We can wait until we upgrade from 2007 (hopefully next year).
The code I added to my overridden version of InternalInitFieldDefs was:
After
if (F.Type_ = adNumeric) and (F.NumericScale = 0) and (F.Precision < 10) then
begin
FieldType := TFieldType(ftInteger);
end
I added
else if (F.Type_ = adNumeric) and (F.NumericScale = 0) and (F.Precision >= 19) then
begin
FieldType := ftLargeint;
end;

How do I query for when a field doesn't begin with a letter?

I'm tasked with adding an option to our search, which will return results where a given field doesn't begin with a letter of the alphabet. (The .StartsWith(letter) part wasn't so hard).
But I'm rather unsure about how to get the results that don't fall within the A-Z set, and equally hoping it generates some moderately efficient SQL underneath.
Any help appreciated - thanks.
In C# use the following construct, assuming db as a data context:
var query = from row in db.SomeTable
where !System.Data.Linq.SqlClient.SqlMethods.Like(row.SomeField, "[A-Z]%")
select row;
This is only supported in LINQ to SQL queries. All rules of the T-SQL LIKE operator apply.
You could also use less effective solution:
var query = from row in db.SomeTable
where row.SomeField[0] < 'A' || row.SomeField[0] > 'Z'
select row;
This gets translated into SUBSTRING, CAST, and UNICODE constructs.
Finally, you could use VB, where there appears to be a native support for the Like method.
Though SQL provides the ability to check a range of characters in a LIKE statement using bracket notation ([a-f]% for example), I haven't seen a linq to sql construct that supports this directly.
A couple thoughts:
First, if the result set is relatively small, you could do a .ToList() and filter in memory after the fact.
Alternatively, if you have the ability to change the data model, you could set up additional fields or tables to help index the data and improve the search.
--EDIT--
Made changes per Ruslan's comment below.
Well, I have no idea if this will work because I have never tried it and don't have a compiler nearby to try it, but the first thing I would try is
var query = from x in db.SomeTable
where x.SomeField != null &&
x.SomeField.Length >= 1 &&
x.SomeField.Substring(0, 1).All(c => !Char.IsLetter(c))
select x;
The possiblility exists that LINQ to SQL fails to convert this to SQL.

Boolean Field in Oracle

Yesterday I wanted to add a boolean field to an Oracle table. However, there isn't actually a boolean data type in Oracle. Does anyone here know the best way to simulate a boolean? Googling the subject discovered several approaches
Use an integer and just don't bother assigning anything other than 0 or 1 to it.
Use a char field with 'Y' or 'N' as the only two values.
Use an enum with the CHECK constraint.
Do experienced Oracle developers know which approach is preferred/canonical?
I found this link useful.
Here is the paragraph highlighting some of the pros/cons of each approach.
The most commonly seen design is to imitate the many Boolean-like
flags that Oracle's data dictionary views use, selecting 'Y' for true
and 'N' for false. However, to interact correctly with host
environments, such as JDBC, OCCI, and other programming environments,
it's better to select 0 for false and 1 for true so it can work
correctly with the getBoolean and setBoolean functions.
Basically they advocate method number 2, for efficiency's sake, using
values of 0/1 (because of interoperability with JDBC's getBoolean() etc.) with a check constraint
a type of CHAR (because it uses less space than NUMBER).
Their example:
create table tbool (bool char check (bool in (0,1));
insert into tbool values(0);
insert into tbool values(1);`
Oracle itself uses Y/N for Boolean values. For completeness it should be noted that pl/sql has a boolean type, it is only tables that do not.
If you are using the field to indicate whether the record needs to be processed or not you might consider using Y and NULL as the values. This makes for a very small (read fast) index that takes very little space.
To use the least amount of space you should use a CHAR field constrained to 'Y' or 'N'. Oracle doesn't support BOOLEAN, BIT, or TINYINT data types, so CHAR's one byte is as small as you can get.
The best option is 0 and 1 (as numbers - another answer suggests 0 and 1 as CHAR for space-efficiency but that's a bit too twisted for me), using NOT NULL and a check constraint to limit contents to those values. (If you need the column to be nullable, then it's not a boolean you're dealing with but an enumeration with three values...)
Advantages of 0/1:
Language independent. 'Y' and 'N' would be fine if everyone used it. But they don't. In France they use 'O' and 'N' (I have seen this with my own eyes). I haven't programmed in Finland to see whether they use 'E' and 'K' there - no doubt they're smarter than that, but you can't be sure.
Congruent with practice in widely-used programming languages (C, C++, Perl, Javascript)
Plays better with the application layer e.g. Hibernate
Leads to more succinct SQL, for example, to find out how many bananas are ready to eat select sum(is_ripe) from bananas instead of select count(*) from bananas where is_ripe = 'Y' or even (yuk) select sum(case is_ripe when 'Y' then 1 else 0) from bananas
Advantages of 'Y'/'N':
Takes up less space than 0/1
It's what Oracle suggests, so might be what some people are more used to
Another poster suggested 'Y'/null for performance gains. If you've proven that you need the performance, then fair enough, but otherwise avoid since it makes querying less natural (some_column is null instead of some_column = 0) and in a left join you'll conflate falseness with nonexistent records.
Either 1/0 or Y/N with a check constraint on it. ether way is fine. I personally prefer 1/0 as I do alot of work in perl, and it makes it really easy to do perl Boolean operations on database fields.
If you want a really in depth discussion of this question with one of Oracles head honchos, check out what Tom Kyte has to say about this Here
The database I did most of my work on used 'Y' / 'N' as booleans. With that implementation, you can pull off some tricks like:
Count rows that are true:
SELECT SUM(CASE WHEN BOOLEAN_FLAG = 'Y' THEN 1 ELSE 0) FROM X
When grouping rows, enforce "If one row is true, then all are true" logic:
SELECT MAX(BOOLEAN_FLAG) FROM Y
Conversely, use MIN to force the grouping false if one row is false.
A working example to implement the accepted answer by adding a "Boolean" column to an existing table in an oracle database (using number type):
ALTER TABLE my_table_name ADD (
my_new_boolean_column number(1) DEFAULT 0 NOT NULL
CONSTRAINT my_new_boolean_column CHECK (my_new_boolean_column in (1,0))
);
This creates a new column in my_table_name called my_new_boolean_column with default values of 0. The column will not accept NULL values and restricts the accepted values to either 0 or 1.
In our databases we use an enum that ensures we pass it either TRUE or FALSE. If you do it either of the first two ways it is too easy to either start adding new meaning to the integer without going through a proper design, or ending up with that char field having Y, y, N, n, T, t, F, f values and having to remember which section of code uses which table and which version of true it is using.

Resources