How can I get fields from a non-generated, aliased table? - java

Table<Record> myTable = DSL.table("myTable");
Table<Record> a = myTable.as("a");
Field<Integer> myField = DSL.field("myField", SQLDataType.INTEGER);
a.field("myField"); // == null
a.field("myField", SQLDataType.INTEGER); // == null
a.field(myField); // == null
I want to use a table expression in a comparison, similar to this. I am using jOOQ just to generate SQL strings; I am not using its code generation for my table types.

I just now saw this comment in the documentation:
A better solution would be any of these:
Field MY_FIELD1 = field(MY_TABLE.getName() + ".MY_FIELD");
Field MY_FIELD2 = field(name(MY_TABLE.getName(), "MY_FIELD"));
I think that this section of the manual might be quite helpful for you: http://www.jooq.org/doc/latest/manual/sql-building/names
This worked for me, but I needed to make some changes because I'm using type-safe fields. I wrote this helper:
private <T> Field<T> field(final Table<Record> table, final Field<T> field) {
return DSL.field(DSL.name(table.getName(), field.getName()), field.getDataType());
}

Related

JOOQ "IN" Query throws null pointer exception

When we try to fetch data with Null values
field(TABLE_NAME.COLUMN_NAME.in(null))
with IN clause
getting null pointer exception.
Maybe because of this.
#Override
public final Condition in(Collection<?> values) {
Field<?>[] fields = new Field[values.size()];
Iterator<?> it = values.iterator();
for (int i = 0; it.hasNext(); i++)
fields[i] = Tools.field(it.next(), this);
return in(fields);
}
In the database, we can provide null in IN clause.
There is an existing "won't fix" issue in jooq https://github.com/jOOQ/jOOQ/issues/3867
There are some alternatives:
check null before IN(Cant do in my case its a really big select statement)
So if I want to make this possible is there any other workaround.
PS: On a similar note "eq" works perfectly fine:
#Override
public final Condition equal(Field<T> field) {
return compare(EQUALS, nullSafe(field, getDataType()));
}
Edit: 'field(TABLE_NAME.COLUMN_NAME.in(null))' here null is a collection.
Your example code doesn't compile:
TABLE_NAME.COLUMN_NAME.in(null)
There are 5 overloads of this in() method in jOOQ 3.14, and as such, you cannot pass the null literal to the in() method. Your real client code may be using a local variable like this:
Collection<?> collection = null;
TABLE_NAME.COLUMN_NAME.in(collection)
There might be a case for when this should behave the same as passing an empty collection, such as Collections.emptyList(), but this isn't what you seem to want. You probably want to pass actual null values inside of that collection, which you can do:
TABLE_NAME.COLUMN_NAME.in(1, null, 2)
But why would you do it? SQL implements three valued logic, meaning that NULL values have no effect in IN predicates, while they have an unintuitive, hardly desired effect in NOT IN predicates (the entire predicate becomes NULL)

jOOQ: best way to get aliased fields (from #as(alias, aliasFunction))

I have to access the same table for multiple references from a "root" table. In order to do so, I'm creating aliases for these tables:
protected final Table<XyzRecord> foo = Tables.XYZ.as("foo", <foo-alias-function>);
protected final Table<XyzRecord> bar = Tables.XYZ.as("bar", <bar-alias-function>);
bar-alias-function would be declared as follows_
protected final Function<Field<?>, String> fooFieldAliasFunction = f -> "foo_" + f.getName();
Now since I'd like to benefit from type safe queries, I need to re-use the same alias-function in my queries to access the fields:
jooq.select()
.from (root)
.leftJoin(foo).on(
checklistTarget.field(fooFieldAliasFunction.apply(Tables.XYZ.ID), Tables.XYZ.ID.getType())
.eq(root.FOO_ID)
)
.leftJoin(bar).on(
checklistTarget.field(barFieldAliasFunction.apply(Tables.XYZ.ID), Tables.XYZ.ID.getType())
.eq(root.BAR_ID)
)
...
;
This seems awefully clumsy (a lot of code) and not terribly efficient (since the aliased field names are probably stored with the aliased table).
I assumed there would be a method on the alias that would give me the aliased field directly (e.g. foo.getField(Tables.XYZ.ID), but that doesn't seem to be the case.
Of course the problem is amplified if I want to select specific fields...
Am I missing something? What's the recommended way of doing this?
Thank you!
I assumed there would be a method on the alias that would give me the aliased field directly (e.g. foo.getField(Tables.XYZ.ID), but that doesn't seem to be the case.
This kind of API would be useful indeed, although the existing Table.field(Field) method shouldn't be retrofitted to assume this behaviour. A new method might be introduced. On the other hand, you could write a simple utility:
<T, R extends Record> Field<T> field(Table<R> table, TableField<R, T> field) {
if (table == foo)
return foo.field(fooFieldAliasFunction.apply(field), field.getType());
else if (table == bar)
return foo.field(barFieldAliasFunction.apply(field), field.getType());
else
throw IllegalArgumentException();
}
And then call it like this:
jooq.select()
.from (root)
.leftJoin(foo).on(field(foo, XYZ.ID).eq(root.FOO_ID))
.leftJoin(bar).on(field(bar, XYZ.ID).eq(root.BAR_ID))
...
;

How to restrict a hibernate criteria to a specific root entity

I'm writing a dynamic query using hibernates criteria language. I'm stunned as I'm unable to find any information regarding restricting the root entity dynamically (i.e. without specifying the id property).
I have an interface IEntity. There are several entities implementing IEntity. Some of them have an ID-property id while others have another ID-property (shadowId).
I want to write a single method covering both cases. Here is what I got so far:
#Override
public boolean querySomething(final IEntity ent) {
final Criteria criteria =
currentSession().createCriteria(HibernateUtils.deproxy(ent.getClass()));
criteria.createAlias("sharedProperty", "prop");
//This does not work:
criteria.add(Property.forName("this").eq(ent));
criteria.setProjection(Projections.count("prop.anotherProperty"));
final Number result = (Number) criteria.uniqueResult();
return result != null && result.longValue() > 0;
}
I would like to avoid to have a if statement like
if (ent instanceof TypeWithPropertyId){
criteria.add(Property.forName("id").eq(ent));
} else {
criteria.add(Property.forName("shadowId").eq(ent));
}
Edit
Note: HibernateUtils.deproxy(Class<?> clazz) removes any Proxy and returns the original class.
As of now, I didn't find anything in the criteria api which could help me. But I found IdentifierEqExpression by accident. Sadly IdentifierEqExpression still needs the ID Value and can not extract it itself. So for the moment I'm using a custom extension:
public class ThisEqualsExpression extends IdentifierEqExpression {
public ThisEqualsExpression(final Object value, final SessionFactory sf) {
super(sf.getClassMetadata(deproxy(value.getClass())).getIdentifier(value, null));
}
}
I would prefer not using this hack-ish approach, but I still like this better than an if-statement:
public boolean querySomething(final IEntity ent) {
final Criteria criteria =
currentSession().createCriteria(HibernateUtils.deproxy(ent.getClass()));
criteria.createAlias("sharedProperty", "prop");
//This does work:
criteria.add(new ThisEqualsExpression(ent, currentSession().getSessionFactory()));
criteria.setProjection(Projections.count("prop.anotherProperty"));
final Number result = (Number) criteria.uniqueResult();
return result != null && result.longValue() > 0;
}
Edit 2:
As requested some clarification:
In pure SQL, i have something like
SELECT COUNT(bar.property)
FROM Foo foo
INNER JOIN Bar bar ON foo.bar_id=bar.id
But I want something like
SELECT COUNT(bar.property)
FROM Foo foo
INNER JOIN Bar bar ON foo.bar_id=bar.id
WHERE foo.id=<some ID> --This is the important part
Back to Java:
Foos ID-Property depends on its type. It may be id (which I used in the SQL example above), but it could also be some other property. I was wondering if there was a way to restrict a criterias root entity without knowing the propertys name.

Hibernate CriteriaAPI and like operator. Swap operands + database independent concatenation - is it possible?

Intro
I have a weird task - to write on Hibernate Criteria API (i.e. in database independent style) SQL query similar to
select * from branch b where '2/5/3/' like b.hier_path + '%'
where + is concatenation operator. Concatenation operator is database dependent '+' in MS SQL, '||' in Oracle etc.
I must use Criteria API (and no way to switch to HQL).
Problem #1 - like operator
Unfortunately, Hibernate allows to write only Criteria based on Java object property:
pCriteria.createCriteria(Branch.class).add(Restrictions.like("hierarchyPath", "2/5/3/%"));
Which is equivalent of
select * from branch where 'hier_path like 2/5/3/%'
I don't know how to swap operands of like operator.
Problem #2 - database independent concatenation
The SQL code must works on Oracle, MS SQL Server, DB2, Postgres, Sybase, MySQL, HSQLDB, Firebird (and some other new relational databases).
What I've got for now is SQL based hack:
Restrictions.sqlRestriction("? like concat({alias}.hier_path,'%')", "2/5/3/", Hibernate.STRING)
Unfortunately, concat is database dependent function that present in most from above mentioned databases (except Postgres and Firebird). The approach is a workaround and could not be used as constant solution (I'll try to add custom functions concat to databases that doesn't have it).
Conclusion
Could anybody propose an improvement to my hack (a database independent SQL) or correction to original CriteriaAPI?
UPDATE 28.09.12
concat functions appears in Postgres 9.1
You could write your own Criterion implementation, which would generate a SQL clause similar to the one you have in your question, except it would use the dialect associated with the criteria query to get the appropriate concat function and delegate the concatenation to this database-dependant concat function.
Thanks #JB Nizet. Code inspired by his ideas:
private class InverseLikeExpression extends SimpleExpression{
private static final String CONST_HQL_FUNCTION_NAME_CONCAT = "concat";
private static final String CONST_LIKE = " like ";
private static final String CONST_LIKE_SUFFIX = "'%'";
private final String propertyName;
protected InverseLikeExpression(String pPropertyName, Object pValue) {
super(pPropertyName, pValue, CONST_LIKE);
propertyName = pPropertyName;
}
#Override
public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
String[] columns = criteriaQuery.getColumnsUsingProjection(criteria, propertyName);
Dialect dialect = criteriaQuery.getFactory().getDialect();
SQLFunction concatFunction = (SQLFunction) dialect.getFunctions().get(CONST_HQL_FUNCTION_NAME_CONCAT);
StringBuffer fragment = new StringBuffer();
if (columns.length>1) fragment.append('(');
for ( int i=0; i<columns.length; i++ ) {
String fieldName = concatFunction.render(Arrays.asList(new Object[] {columns[i], CONST_LIKE_SUFFIX}), criteriaQuery.getFactory());
fragment.append("?").append( getOp() ).append(fieldName);
if ( i<columns.length-1 ) fragment.append(" and ");
}
if (columns.length>1) fragment.append(')');
return fragment.toString();
}
}

Hibernate: Parse/Translate HQL FROM part to get pairs class alias, class name

Can anyone point me out, how can I parse/evaluate HQL and get map where key is table alias and value - full qualified class name.
E.g. for HQL
SELECT a.id from Foo a INNER JOIN a.test b
I wish to have pairs:
a, package1.Foo
b. package2.TestClassName
It's relatively easy to do for result set
HQLQueryPlan hqlPlan = ((SessionFactoryImpl)sf).getQueryPlanCache().getHQLQueryPlan( getQueryString(), false, ((SessionImpl)session).getEnabledFilters() );
String[] aliases = hqlPlan.getReturnMetadata().getReturnAliases();
Type[] types = hqlPlan.getReturnMetadata().getReturnTypes();
See details here.
Hardly a good way of doing it, but it seems you can get the AST through some internal interfaces and traverse this:
QueryTranslator[] translators = hqlPlan.getTranslators();
AST ast = (AST)((QueryTranslatorImpl)translators[0]).getSqlAST();
new NodeTraverser(new NodeTraverser.VisitationStrategy() {
public void visit(AST node) {
if(node.getType() == SqlTokenTypes.FROM_FRAGMENT || node.getType() == SqlTokenTypes.JOIN_FRAGMENT) {
FromElement id = (FromElement)node;
System.out.println(node+": "+id.getClassAlias()+" - "+id.getClassName());
}
}
}).traverseDepthFirst(ast);
So this seems to retrieve the alias-mappings from the compiled query, but I would be very careful using this solution: it typecasts objects to subclasses not usually visible to a hibernate-client and interprets the AST based on guessing the semantics of the different nodes. This might not work on all HQL-statements, and might not work, or have different behaviour, on a future hibernate-version.
I found right solution for my question. Your original post was almost correct except that part:
if(node.getType() == SqlTokenTypes.FROM_FRAGMENT || node.getType() == SqlTokenTypes.JOIN_FRAGMENT) {
FromElement id = (FromElement)node;
System.out.println(node+": "+id.getClassAlias()+" - "+id.getClassName());
}
Please correct your answer answer and I accept it.

Categories