Corda Custom Query with field name taken as input - kotlin

I am using Corda 4.0 and I want to run a custom query with dynamic column name (colN) and column value (colC). Here is my code for the query building
builder {
val index: CriteriaExpression.ColumnPredicateExpression<Any,String> = getField(colN,CarSchemaV1.PersistentCar::class.java).equal(colC)
val customCriteria = QueryCriteria.VaultCustomQueryCriteria(index)
}
But I am getting a error in compile time
Type parameter bound for L in constructor VaultCustomQueryCriteria<L: PersistableState>(expression: CriteriaExpression<L,Boolean>,...) is not satisfied: inferred typed Any is not a subtype of StatePersistable

the issue here has to do with your use of Any in the generics of CriteriaExpression.ColumnPredicaeExpression<Any, String>. As indicated by the error message, instead of using Any you should be using a subtype of StatePersistable
According to the docs: net.corda.core.schemas.StatePersistable
Marker interface to denote a persistable Corda state entity that will always have a transaction id and index
Your schema CarSchemaV1.PersistentCar::class.java likely implements PersistentState. Try adding that into your generics.
val index: CriteriaExpression.ColumnPredicateExpression<CarSchemaV1.PersistentCar,String> = getField(colN,CarSchemaV1.PersistentCar::class.java).equal(colC)

Related

Jooq: JsonConverter not converting jsonb into list of class when fetching data

This is a continuation of a first question I asked here: Jooq: How can I map a JSONB column to a Kotlin data class field?
Although I'm able to create new records just fine with the changes mentioned there, I'm not being able to fetch data like so:
fun findAllTrackedEvents(): List<TrackedEvent> {
return dslContext.select(*TRACKED_EVENT.fields())
.from(TRACKED_EVENT)
.fetchInto(TrackedEvent::class.java)
}
It seems that jackson is mapping the rows into LinkedHashMaps instead of mapping them into the fields of the Metadata data class. This is the error I'm getting:
Resolved [org.springframework.http.converter.HttpMessageNotWritableException: Could not write JSON:
object is not an instance of declaring class; nested exception is com.fasterxml.jackson.databind.JsonMappingException:
object is not an instance of declaring class (through reference chain: java.util.ArrayList[0]->com.my.project.tracked_event.TrackedEvent["metadata"]->java.util.ArrayList[0]->java.util.LinkedHashMap["tableRef"])]
data class TrackedEvent(
val id: UUID,
// other fields
val metadata: List<Metadata> // this metadata field in the database is of type jsonb
)
data class Metadata(
val tableRef: String,
val value: UUID
)
So it can convert the field properly when inserting but not when fetching?
In my previous answer, I suggested you use arrays instead of lists. This had a reason. Consider this:
fun main() {
val a: Array<Int?> = arrayOf(1)
println(a::class.java)
val b: List<Int?> = listOf(1)
println(b::class.java)
}
It prints:
class [Ljava.lang.Integer;
class java.util.Collections$SingletonList
As you can see, while arrays are reified on the JVM, other generic types are not, and the T type variable of List<T> is erased. It is possible that Jackson cannot figure out the correct type to unmarshal at runtime using reflection, despite all the type information being available at compile time.
I would just use Array<Metadata> instead. Alternatively, of course, you can attach a custom converter to the column, instead of using the out of the box <jsonConverter>. That way, you're in full control of the mapping.

Map of generic interfaces in Kotlin

I stuck with some simple thing) Let's say I have following:
interface IMessagePayload // marker interface
data class IdPayload(
val id: Long
) : IMessagePayload
data class StringPayload(
val id: String,
) : IMessagePayload
Then I have a class:
data class Message<T : IMessagePayload>(
val id: String,
val payload: T,
)
Also I have some interface describing processor of this message:
interface IMessageProcessor<T : IMessagePayload> {
fun process(message: Message<T>)
}
And some implementation:
class ProcessorImpl : IMessageProcessor<IdPayload> {
override fun process(message: Message<IdPayload>) {
}
}
Now I wanna have a map of such processors. Lets use some enum type as a keys of this map:
enum class ActionType {
UPDATE,
DELETE,
ADD
}
private var map = mutableMapOf<ActionType, IMessageProcessor<IMessagePayload>>()
map[ActionType.ADD] = ProcessorImpl() // <-- error here
And that's where the problem occurs. I cannot put my ProcessorImpl into this map. The compiler says that there is an error: Type mismatch. Required: IMessageProcessor. Found: ProcessorImpl().
I could declare the map in the following way (using star projection):
private var map = mutableMapOf<ActionType, IMessageProcessor<*>>()
But in this case I cannot call processors's process method fetching it from the map by key first:
map[ActionType.ADD]?.process(Message("message-id", IdPayload(1))) // <-- error here
Compiler complains: Type mismatch. Required nothing. Found Message<IdPayload>
What am I doing wrong? Any help is appreciated.
This is about variance.
IMessageProcessor is defined as interface IMessageProcessor<T : IMessagePayload>; it has one type parameter, which must be IMessagePayload or a subtype.
But it is invariant in that type parameter; an IMessageProcessor< IdPayload> is not related to an IMessageProcessor<IMessagePayload>.  In particular, it's not a subtype.
And your map is defined with a value type IMessageProcessor<IMessagePayload>.  So its value cannot be an IMessageProcessor< IdPayload>, because that's neither the value type, nor a subtype.  Hence the compile error.
In this case, the simplest way to get it to compile is to change your map:
private var map = mutableMapOf<ActionType, IMessageProcessor<out IMessagePayload>>()
The only difference there is the out; that tells the compiler that the value IMessageProcessor is covariant in its type parameter.  (It may help to think of out as meaning ‘…or any subtype’.  Similarly, you could make it contravariant by using in, which you might think of as ‘…or any supertype’.)
This lets you store in the map an IMessageProcessor for any subtype of IMessagePayload.
However, if you do that, you'll find that you can't use any value you pull out of your map — because it can't tell which messages the processor can handle, i.e. which subtype of IMessagePayload it works for!  (The compiler expresses this as expecting a type parameter of Nothing.)
In general, it's often better to specify variance on the interface or superclass itself (declaration-site variance) rather than the use-site variance shown above.  But I can't see a good way to do that here, because you have multiple generic classes, and they interact in a complicated way…)
Think for a moment what IMessageProcessor's type parameter means: it's the type of message that the processor can consume. So an IMessageProcessor<A> can handle messages of type Message<A>.
Now, a subtype must be able to do everything its supertype can do (and usually more) — otherwise you can't drop that subtype anywhere that's expecting to use the supertype.  (That has the grand name of the Liskov substitution principle — but it's really just common sense.)
So an IMessageProcessor<B> is a subtype of IMessageProcessor<A> only if it can handle at least all the messages that an IMessageProcessor<A> can.  This means it must accept all messages of type Message<A>.
But Message is invariant in its type parameter: a Message<B> is not directly related to a Message<A>.  So you can't write a processor that handles them both.
The most natural solution I can find is to specify variance on both Message and IMessageProcessor:
data class Message<out T : IMessagePayload>( /*…*/ )
interface IMessageProcessor<in T : IMessagePayload> { /*…*/ }
And then use a wildcard in your map to make it explicit that you don't know anything about the type parameters of its values:
private var map = mutableMapOf<ActionType, IMessageProcessor<*>>()
That lets you safely store a ProcessorImpl() in the map.
But you still have to use an (unchecked) cast on the values you pull out of the map before you can use them:
(map[ActionType.ADD] as IMessageProcessor<IdPayload>)
.process(Message("4", IdPayload(4L)))
I don't think there's any easy way around that, because the problem is inherent in having values which are processors that can handle only some (unknown) types of message.
I'm afraid the best thing would be to have a rethink about what these classes mean and how they should interact, and redesign accordingly.

Realm: How to tell if a field is nullable via DynamicRealm?

Does DynamicRealm offer a way to determine if a field is nullable on a kotlin RealmModel subclass?
For example, given the following realm models:
open class IntObj(
var i: Int = 1
): RealmObject()
open class NullIntObj(
var i: Int? = null
): RealmObject()
If I fetch a dynamic realm object representation of each type and try to determine the field type of parameter "i" via dynamicRealmObj.getFieldType("i"), they both return "INTEGER".
Is there a way I can determine the nullability of a field on a dynamic realm object? Do I have to use reflection to try and determine what the underlying Java field is annotated with?
Well I'm an idiot - turns out dynamicRealm.isNullable(fieldName) totally exists :)

JDBI: querying a string column with kotlin

I have a column title in a SQL table myTable.
Using Kotlin and JDBI, how would I go about getting all the distinct entries in this table?
This is what I have tried so far:
val jdbi = Jdbi.create("...url", "...user", "...password")
fun getTitles(): List<String> = jdbi.withHandleUnchecked { handle ->
handle.createQuery("select distinct(title) from myTable;")
.mapTo(String.javaClass)
.list()
However, this gives me the following exception:
A bean, Companion was mapped which was not instantiable (cannot find appropriate constructor)
What's going wrong here?
Apparently, String.javaClass is not what I want here (as it is a different type than is required). It is String::class.java.

Hibernate SQL transformation fails for Enum field type

I am using a SQL query and then transforming the result using Hibernates's Transformers.aliasToBean().
One of the columns in my query is an enum. The transformation somehow fails for the enum. What should I do? Which datatype should I use? I want more than 1 character to transform the result into my enum type.
This is how the simplified version of my query/code looks like (b is an enum in the table profiles):
session.createSQLQuery("select a, b from profiles").setResultTransformer(Transformers.aliasToBean(Profile.class))
.list();
Exception : expected type: Foo.ProfileStateEnum, actual value: java.lang.Character
Assuming that the java enum type that corresponds to column b is Foo.ProfileStateEnum, the following code snippet should work for you. (I tested with Hibernate 4.1.6)
import java.util.Properties;
import org.hibernate.type.Type;
import org.hibernate.type.IntegerType;
import org.hibernate.internal.TypeLocatorImpl.TypeLocatorImpl;
import org.hibernate.type.TypeResolver.TypeResolver;
import org.hibernate.type.EnumType;
Properties params = new Properties();
params.put("enumClass", "Foo.ProfileStateEnum");
params.put("type", "12"); /*type 12 instructs to use the String representation of enum value*/
/*If you are using Hibernate 5.x then try:
params.put("useNamed", true);*/
Type myEnumType = new TypeLocatorImpl(new TypeResolver()).custom(EnumType.class, params);
List<Profile> profileList= getSession().createSQLQuery("select a as ID, b from profiles")
.addScalar("ID", IntegerType.INSTANCE)
.addScalar("b", myEnumType )
.setResultTransformer(Transformers.aliasToBean(Profile.class))
.list();
I found two ways to achieve it.
Use org.hibernate.type.CustomType with org.hibernate.type.EnumType(put either EnumType.NAMED or EnumType.TYPE, see EnumType#interpretParameters). Like below:
Properties parameters = new Properties();
parameters.put(EnumType.ENUM, MyEnum.class.getName());
// boolean or string type of true/false; declare database type
parameters.put(EnumType.NAMED, true);
// string only; declare database type
parameters.put(EnumType.TYPE, String.valueOf(Types.VARCHAR));
EnumType<MyEnum> enumType = new EnumType<>();
enumType.setTypeConfiguration(new TypeConfiguration());
enumType.setParameterValues(parameters);
CustomType customEnumType = new CustomType(enumType);
Another simple way. Use org.hibernate.type.StandardBasicTypeTemplate with org.hibernate.type.descriptor.sql.*TypeDescriptor. Like below:
StandardBasicTypeTemplate<MyEnum> enumType =
new StandardBasicTypeTemplate<>(VarcharTypeDescriptor.INSTANCE,
new EnumJavaTypeDescriptor<>(MyEnum.class));
Let's see why you are getting this exception.
From the question it is obvious that you have used #Enumerated(EnumType.STRING) annotation for the field 'b' in you model class. So the field is an enum for your model class and a varchar for your database. Native SQL is not concerned about you model class and returns what ever is there in the database table as it is. So in your case, the SQLQuery you are using will return a String for 'b' instead of a ProfileStateEnum type. But your setter method for 'b' in the Profile class takes a ProfileStateEnum type argument.
Thus you get the exception "expected type: Foo.ProfileStateEnum, actual value: java.lang.Character"
You can use Aliasing to solve the problem.
What I suggest is, alias your column with any name you want and create a setter method for that alias in your model/dto.
For example, lets alias your column as 'enumStr'.
Then your query will look like this : "select a, b as enumStr from profiles"
Now, create a setter method for that alias in the your Profile class.
(Assuming that the enum ProfileStateEnum can have any of the two values STATE1 and STATE2)
public void setEnumStr(String str){
/*Convert the string to enum and set the field 'b'*/
if(str.equals(ProfileStateEnum.STATE1.toString())){
b = ProfileStateEnum.STATE1;
} else {
b = ProfileStateEnum.STATE2;
}
}
Now on transforming, the setter for the alias setEnumStr(String) will be invoked instead of setter for the field setB(ProfileStateEnum) and the string will be converted and saved to the type you want without any exceptions.
I am a beginner in Hibernate and the solution worked for me. I am using PostgreSQL. But I believe it works for other databases too.