Nhibernate and like statement on XML field - nhibernate

I have a wrapped fluent nhibernate framework that I'm reusing and have no control over the actual mapping.
In my entity object I have a property mapped as string to an XML column in sql.
Hence when I run a query like:
var myResult = (from myTable in DataManager.Session.Query<Table>()
where myTable.thatXmlFieldWhichIsMappedAsString.Contains(AnXmlSnippet))
select myTable).FirstOrDefault();
It is trying to use the LIKE operator in SQL which is invalid on that column type.
How can I get around this without having to select all the rows and converting to List first?

In case, that we do not need .Query() (LINQ), and we can use Criteria query or QueryOver, we can use conversion:
// the projection of the column with xml
// casted to nvarchar
var projection = Projections
.Cast(NHibernateUtil.StringClob
, Projections.Property("thatXmlFieldWhichIsMappedAsString"));
// criteria filtering with LIKE
var criteria = Restrictions.Like(projection, "searched xml");
// query and result
var query = session.QueryOver<MyEntity>()
.Where(criteria)
;
var result = query
.SingleOrDefault<MyEntity>()
;
From my experience this could lead to conversion into small nvarchar(255) - sql server... Then we can do it like this:
var projection = Projections
.SqlProjection("CAST(thatXmlFieldWhichIsMappedAsString as nvarchar(max)) AS str"
, new string[]{}
, new NHibernate.Type.IType[]{}
);

Related

PDI: How to change unspecified fields meta type?

I'm handling random tables from a database, I have a field with table's schema, and another with table's name, that way I'm able to do a table input like the following inside a child transformation:
SELECT * FROM ${SCHEMA}.${TABLENAME};
After getting the table values as columns, I want to dynamically generate an SQL with dynamic WHERE clauses, with something like this within a User Defined Java class:
Object[] in = getRow();
String sql = String.format("SELECT %s FROM %s.%s WHERE ",
getParameter("KEYS"),
getParameter("SCHEMA"),
getParameter("TABLE"));
Object[] out = createOutputRow(in, data.outputRowMeta.size());
ArrayList<String> fieldNames = new ArrayList<String>(Arrays.asList(data.inputRowMeta.getFieldNames()));
for(String field: fieldNames){
sql += String.format("\"%s\" = \'%s\' AND ", field, get(Fields.In, field).getString(in));
get(Fields.Out, field).setValue(out, get(Fields.In, field).getString(in));
}
sql = sql.substring(0, sql.length()-4);
sql += ";";
Resulting in a column like this:
SELECT keys, from, the, table FROM SCHEMA.TABLE WHERE "unknown_field" = 'value' AND "unknown_field_1" = '0';
Now, that's all good until I receive a non String type from the SELECT, where I can't do get(Fields.In, field).getString(in)
since it returns
Conversion error: field Integer(9): There was a data type error: the data type of java.lang.String.object [0] does not correspond to value meta [Integer (9)]
Can you change every unspecified field's meta type? If not, is there a way to cast every non String field type within the custom Java Class to String?

operator (plus add) in entity framework core send an error

When using sql select I need a counter for my sequence row like this:
var result = from d in data
select new[]
{
Convert.ToString((count++))
};
but this syntax gives the following error message:
An expression tree may not contain an assignment operator Vehicle.app..NETCoreApp,Version=v1.0
When using LINQ (e.g. from x in data select { ... }) the expression in the selection is translated into a SQL statement and executed in the database. How would an assignment like count++ work in a database? I think you're better off with selecting the records from the database and apply the sequence number on the in memory list of data rather than on the queryable collection which represents the database. For example:
var data = (from d in data select new { ... }).ToList();
var result = data.Select(new { id = Convert.ToString(count++), ... }).ToList();

Setting a Clob value in a native query

Oracle DB.
Spring JPA using Hibernate.
I am having difficulty inserting a Clob value into a native sql query.
The code calling the query is as follows:
#SuppressWarnings("unchecked")
public List<Object[]> findQueryColumnsByNativeQuery(String queryString, Map<String, Object> namedParameters)
{
List<Object[]> result = null;
final Query query = em.createNativeQuery(queryString);
if (namedParameters != null)
{
Set<String> keys = namedParameters.keySet();
for (String key : keys)
{
final Object value = namedParameters.get(key);
query.setParameter(key, value);
}
}
query.setHint(QueryHints.HINT_READONLY, Boolean.TRUE);
result = query.getResultList();
return result;
}
The query string is of the format
SELECT COUNT ( DISTINCT ( <column> ) ) FROM <Table> c where (exact ( <column> , (:clobValue), null ) = 1 )
where "(exact ( , (:clobValue), null ) = 1 )" is a function and "clobValue" is a Clob.
I can adjust the query to work as follows:
SELECT COUNT ( DISTINCT ( <column> ) ) FROM <Table> c where (exact ( <column> , to_clob((:stringValue)), null ) = 1 )
where "stringValue" is a String but obviously this only works up to the max sql string size (4000) and I need to pass in much more than that.
I have tried to pass the Clob value as a java.sql.Clob using the method
final Clob clobValue = org.hibernate.engine.jdbc.ClobProxy.generateProxy(stringValue);
This results in a java.io.NotSerializableException: org.hibernate.engine.jdbc.ClobProxy
I have tried to Serialize the Clob using
final Clob clob = org.hibernate.engine.jdbc.ClobProxy.generateProxy(stringValue);
final Clob clobValue = SerializableClobProxy.generateProxy(clob);
But this appears to provide the wrong type of argument to the "exact" function resulting in (org.hibernate.engine.jdbc.spi.SqlExceptionHelper:144) - SQL Error: 29900, SQLState: 99999
(org.hibernate.engine.jdbc.spi.SqlExceptionHelper:146) - ORA-29900: operator binding does not exist
ORA-06553: PLS-306: wrong number or types of arguments in call to 'EXACT'
After reading some post about using Clobs with entities I have tried passing in a byte[] but this also provides the wrong argument type (org.hibernate.engine.jdbc.spi.SqlExceptionHelper:144) - SQL Error: 29900, SQLState: 99999
(org.hibernate.engine.jdbc.spi.SqlExceptionHelper:146) - ORA-29900: operator binding does not exist
ORA-06553: PLS-306: wrong number or types of arguments in call to 'EXACT'
I can also just pass in the value as a String as long as it doesn't break the max string value
I have seen a post (Using function in where clause with clob parameter) which seems to suggest that the only way is to use "plain old JDBC". This is not an option.
I am up against a hard deadline so any help is very welcome.
I'm afraid your assumptions about CLOBs in Oracle are wrong. In Oracle CLOB locator is something like a file handle. And such handle can be created by the database only. So you can not simply pass CLOB as bind variable. CLOB must be somehow related to database storage, because this it can occupy up to 176TB and something like that can not be held in Java Heap.
So the usual approach is to call either DB functions empty_clob() or dbms_lob.create_temporary (in some form). Then you get a clob from database even if you think it is "IN" parameter. Then you can write as many data as you want into that locator (handle, CLOB) and then you can use this CLOB as a parameter for a query.
If you do not follow this pattern, your code will not work. It does not matter whether you use JPA, SpringBatch or plan JDBC. This constrain is given by the database.
It seems that it's required to set type of parameter explicitly for Hibernate in such cases. The following code worked for me:
Clob clob = entityManager
.unwrap(Session.class)
.getLobHelper()
.createClob(reader, length);
int inserted = entityManager
.unwrap(org.hibernate.Session.class)
.createSQLQuery("INSERT INTO EXAMPLE ( UUID, TYPE, DATA) VALUES (:uuid, :type, :data)")
.setParameter("uuid", java.util.Uuid.randomUUID(), org.hibernate.type.UUIDBinaryType.INSTANCE)
.setParameter("type", java.util.Uuid.randomUUID(), org.hibernate.type.StringType.INSTANCE)
.setParameter("data", clob, org.hibernate.type.ClobType.INSTANCE)
.executeUpdate();
Similar workaround is available for Blob.
THE ANSWER: Thank you both for your answers. I should have updated this when i solved the issue some time ago. In the end I used JDBC and the problem disappeared in a puff of smoke!

NHibernate native SQL in WHERE clause

I have a Geography column in a table in SQL Server and would like to filter rows with a specific geometry type, e.g. all records where geometry type is 'Point'
The SQL query would look like
select * from GeometryTable g where g.Geography.STGeometryType() = 'Point'
How can I create a criteria for that? The criteria is going to be used with other criterias
criteria.Add(Restrictions.Add(<Geography.STGeometryType()>, some.Value)
Thanks
Use this syntax:
var criteria = session.CreateCriteria<Geometry>();
criteria.Add
(
Expression.Sql(" {alias}.[Geography].STGeometryType() = ? "
, "Point" // a place for your parameter
, NHibernate.NHibernateUtil.String)
);
var list = criteria.List<Geometry>();

multiple parameter "IN" prepared statement

I was trying to figure out how can I set multiple parameters for the IN clause in my SQL query using PreparedStatement.
For example in this SQL statement, I'll be having indefinite number of ?.
select * from ifs_db where img_hub = ? and country IN (multiple ?)
I've read about this in
PreparedStatement IN clause alternatives?
However I can't figure it out how to apply it to my SQL statement above.
There's not a standard way to handle this.
In SQL Server, you can use a table-valued parameter in a stored procedure and pass the countries in a table and use it in a join.
I've also seen cases where a comma-separated list is passed in and then parsed into a table by a function and then used in a join.
If your countries are standard ISO codes in a delimited list like '#US#UK#DE#NL#', you can use a rather simplistic construct like:
select * from ifs_db where img_hub = ? and ? LIKE '%#' + country + '#%'
Sormula will work for any data type (even custom types). This example uses int's for simplicity.
ArrayList<Integer> partNumbers = new ArrayList<Integer>();
partNumbers.add(999);
partNumbers.add(777);
partNumbers.add(1234);
// set up
Database database = new Database(getConnection());
Table<Inventory> inventoryTable = database.getTable(Inventory.class);
ArrayListSelectOperation<Inventory> operation =
new ArrayListSelectOperation<Inventory>(inventoryTable, "partNumberIn");
// show results
for (Inventory inventory: operation.selectAll(partNumbers))
System.out.println(inventory.getPartNumber());
You could use setArray method as mentioned in the javadoc below:
http://docs.oracle.com/javase/6/docs/api/java/sql/PreparedStatement.html#setArray(int, java.sql.Array)
Code:
PreparedStatement statement = connection.prepareStatement("Select * from test where field in (?)");
Array array = statement.getConnection().createArrayOf("VARCHAR", new Object[]{"AA1", "BB2","CC3"});
statement.setArray(1, array);
ResultSet rs = statement.executeQuery();