Implementing primary key agnostic Room object equivalence - kotlin

Consider the following simple data class being used as a Room #Entity
#Entity
data class SimpleDataClass(
val name: String,
val timestamp: String,
val isInvalid: Boolean = false,
#PrimaryKey(autoGenerate = true) val id:Int=0
)
The functionality I am trying to implement is that two SimpleDataClass objects should be considered same if all there properties are same regardless of their id (which is only added to the class structure because it's going to be stored in a database). Eg. the timestamp only stores the dayOfMonth and the user did the same thing over many months
Note that as database records, they should be considered separate since the user really did generate two records hence their different auto incremented primary keys.
One can do this by writing explicit equals that ignores id comparison. But then one also needs to write other comparison functions like hashCode, toString etc. which I am trying to avoid (let the compiler generate them)
To let the compiler ignore id , I followed the advise here
#Entity
data class SimpleDataClass(
val name: String,
val timestamp: String,
val isInvalid: Boolean = false,
) {
#PrimaryKey(autoGenerate = true)
var id: Int=0
}
Note that id is now a var to allow for its setter
This achieves what I was looking for except that it breaks copying. When a record read from the database as a SimpleDataClass object is copied (via simpleDataClass.copy()), its id isn't transferred to the copy. To remedy that I am using
old.copy(/*change some params, or not*/).also { it.id = old.id}
Neither method feels satisfactory. How to achieve this?

Related

How to group objects in a list by two fields?

I would like to group a list of objects basing on two fields in those objects.
Let's say I have an object like this:
data class ItemDataDTO(
val itemId: BigInteger?,
val sequence: BigInteger?,
val serialNumber: String?,
val pickingId: String?,
val runId: String? = null,
val warehouse: String?,
)
Now I have a list of ItemDataDTO containing a lot of items. I would like to group them by runId and pickingId (because I need those items that have the same pickingId and runId grouped somehow.)
val items: List<ItemDataDTO> = someItemRepository.getItemsForWarehouse("someWarehouseId")
val groupedItems = items.groupBy({ it.runId }, { it.pickingId })
This doesn't work. I found out that I could use groupingBy() function along with a Triple, but I want just them to be grouped by two values...
val groupedItems = items.groupingBy { Triple(it.runId, it.pickingId, null) }
But this doesn't work as well. I tried to add a third parameter instead of null, using it.warehouse:
val groupedItems = items.groupingBy { Triple(it.runId, it.pickingId, it.warehouse) }
It returns an instance of Grouping<ItemDataDTO, Triple<String?, String?, String?>> and I'm not sure what to do with this object.
What could I do to properly group those objects?
In a perfect world, I would like to transform this list to a list of something like:
data class PickingList(
val runId: String,
val pickingId: String,
val items: List<ItemDataDTO>,
)
So the output would be a List<PickingList>.
There's nothing special about it really! groupBy takes a keySelector function which returns some value to be used as a key for that item. So if you want to match on two properties, that key item needs to be composed of those two values.
A Triple with two items is a Pair, so you can just do this:
// just FYI, "it.runId to it.pickingId" is shorthand for a Pair - you see it more
// when defining key/value pairs for maps though. "Pair(x, y)" might read better here
// since you're really just combining values, not describing how one relates to the other
items.groupBy { Pair(it.runId, it.pickingId) }
So each item will produce a Pair with those two values. Any other items with a Pair that matches (as far as the equals function goes) will be put into the same group. It's a bit like adding to a Map, except that if a key already exists, the value is added to a list instead of overwriting the previous value.
You can do that with any key really. Pair and Triple are just quick, general convenience classes for bundling a few items together - but a lot of the time it's better to define your own data structure, e.g. using a data class. So long as two instances with the same data are equal, they count as the same key for grouping.
As for the output you want, with the PickingList... you could use something like that for your grouping operation - but in that case you'd have to pretty much reimplement groupBy yourself. You'd have to take an item, and work out its composite key from the properties you want to consider. Then you'd need to find a match for that key in some store you've created for your groups
If it's a list of PickingLists, you'd need to go through each one, comparing its IDs to the ones you want, adding to its list if you find a match and creating the object if you can't find it.
If you're storing a map of Pair(id1, id2) -> PickingList then that's close to how groupBy works anyway, in terms of generating a key for lookups. In that case, you might want to just use groupBy to group all your items, and then transform the final map:
items.groupBy { Pair(it.runId, it.pickingId) }
.map { (ids, list) ->
PairingList(runId = ids.first, pickingId = ids.second, items = list)
}
This takes every map entry (a Pair of IDs and the list of all things grouped by those IDs) and uses it to create a PairingList from that key/value data. Basically, once you've grouped all your data, you transform it into the data structures you want to work with.
This is also a good example of why your own data class might be better than just using a Pair - it.first doesn't really tell you what that value is in the Pair, just that it's the first of the two values. Whereas
data class IdCombo(val runId: String, val pickingId: String)
works the same as a Pair, but the properties have useful names and make your code much more readable and less prone to bugs:
map { (ids, list) ->
// didn't even bother with the named arguments, since the names are in
// the ids object now!
PairingList(ids.runId, ids.pickingId, items = list)
}

How to compare two lists and insert values based on one into the other by a property in kotlin?

I am new to kotlin and I am trying to loop through two list and insert email in people.
data class User(val id:String,val name: String,val email: String)
data class Person(val id:String,val name: String, val email: String="")
//Input
val users = listOf(User("1","John","john#a.com"),User("2","Doe","Doe#a.com"))
val people = listOf(Person("1","John"),Person("2","Doe"))
//expected
val userToPerson = listOf(Person("1","John","john#a.com"),Person("2","Doe","Doe#a.com"))
I am trying with this.
val map = people.map { it ->
{
val foundUser = users.find { user -> user.id == it.id }
if (foundUser != null) {
it.email = foundUser.email
}
}
}
map.forEach(System.out::print)
I am getting error for foundUser.isNotNull() here Unresolved reference: isNotNull
Updated with suggested:
() -> kotlin.Unit() -> kotlin.Unit
I am trying to convert a list of users to a list of people. They both have their ids as common.
I want to update people Person class corresponding to their user email.
All people do not have email. But all users have the email.
So, the final result would have people with email. If there is a person with no matching id, we can skip that data.
First, calling find for every person is not only a bit awkward to write, it's also (which is far worse) inefficient. (It takes time proportional to the square of the number of people, which means it will perform really badly as the number of people gets large.)
To fix that, I'd create a map from an ID to its User:
val usersById = users.associateBy{ it.id }
We can then look up users by their ID quickly, in a way which scales well.
Armed with that, the solution can be fairly straightforward. Here's one which creates new* Person objects:
val userToPerson = people.map{ person ->
val user = usersById[person.id]
if (user != null && user.email != person.email)
Person(person.id, person.name, user.email)
else
person
}
This solution is a little longer than necessary, but I hope it's easy to read and understand. It also avoids creating Person objects unless necessary, for efficiency. And when there's no corresponding User, it uses the existing Person.
* As the question is currently written, Person's fields are immutable, so the existing Person objects can't be updated with a new email address. That leads naturally into a functional style.
That's not necessarily a bad thing; immutability has many benefits, such as being easier to think about, and thread safety. It can also allow some compiler optimisations. However, if you're not careful, it can generate lots of temporary objects, which can can reduce efficiency (due to cache misses, constructor calls, and then more frequent garbage collections).
The alternative would be to make Person mutable, and do all the updates in-place — which is the traditional imperative style that most of us started from.
Both approaches are valid; which one you choose is a trade-off involving safety, maintainability, and performance — Kotlin supports both.

Kotlin and Gson Fetch from API a JSON object with any number of Keys and store in Room database

Suppose I have a service that returns some JSON where I know before hand some keys, but not the others.
I want to store the data in my room database such that the known properties on the JSON are properties on my entity, but the entire payload of unknown keys is also stored.
I can do it like so:
Model
#Entity
data class Whatever(
#PrimaryKey
val id: String,
val somethingElse: JsonObject?)
Usage
val gson = GsonBuilder()
.disableHtmlEscaping()
.create()
val json = "{\"id\":\"42\",\"extra\":[{\"z\":\"banana\"}]}"
val raw = gson.fromJson(this, JsonObject::class.java)
val model = gson.fromJson(this, Whatever::class.java)
model.somethingElse = raw
// save to database here
This works but it seems plenty inefficient to me (I need to do this to hundreds of thousands of records).
Is there a way to streamline it such that I only have to make one gson.fromJson() call?
Or, better yet, is there some other and better way to store my arbitrary data in Room.

Kotlin: get members of a data class by reflection in the order they have been defined

Assume the following simple example data class:
data class SomeDataClass(
var id: String,
var name: String,
var deleted: String
)
With the following code it is possible to get the properties (and set or get their values):
import kotlin.reflect.full.memberProperties
val properties = SomeDataClass::class.memberProperties
print(properties.map { it.name }) // prints: [deleted, id, name]
The map within the print statement will return a List with the name of the properties in alphabetical order. I need the list in the order they have been defined in the source code, in this case: [id, name, deleted].
It doesn't seem achievable purely through reflection. The only solution I could come up with is to use a helper class defining the order:
val SomeDataClass_Order = listOf("id", "name", "deleted")
This wouldn't be a problem for one or two classes, but it is for hundreds of data classes with the largest one having up to almost one hundred properties.
Any idea would be welcome. I do not need detailed code, rather hints (like parsing the source code, annotations, etc).
If all the properties are declared in the primary constructor, you could "cheat":
val propertyNames = SomeDataClass::class.primaryConstructor!!.parameters.map { it.name }
If you want the KPropertys:
val properties = propertyNames.map { name ->
SomeDataClass::class.memberProperties.find { it.name == name }
}
This unfortunately doesn't find the properties that are declared in the class body.
I don't know about other platforms, but on Kotlin/JVM, the order in which the backing fields for the properties are generated in the class file is not specified, and a quick experiment finds that the order (at least for the version of kotlinc that I'm using right now), the order is the same as the declaration order. So in theory, you could read the class file of the data class, and find the fields. See this related answer for getting the methods in order. Alternatively, you can use Java reflection, which also doesn't guarantee any order to the returned fields, but "just so happens" to return them in declaration order:
// not guaranteed, might break in the future
val fields = SomeDataClass::class.java.declaredFields.toList()
If you do want to get the properties declared inside the class body in order too, I would suggest that you don't depend on the order at all.

kotlin with jooq and write table models manually without code generation

I'm experimenting with jOOQ and Kotlin and seen some tutorials and docs and it looks really nice.
But if there is something very annoying with jOOQ is the code generation. It seems too complex, and eventually impossible to maintain. I decided to create my own table models (similar to how hibernate works).
I created two table models:
User
data class User(
val id: String = UUID.randomUUID().toString(),
val name: String,
val email: String,
val password: String? = null
) {
companion object {
val TABLE: Table<Record> = DSL.table("user")
val ID: Field<String> = DSL.field("id", String::class.java)
val USER_NAME: Field<String> = DSL.field("user_name", String::class.java)
val EMAIL: Field<String> = DSL.field("email", String::class.java)
val PASSWORD: Field<String> = DSL.field("password", String::class.java)
}
}
Followers
data class Followers(
val id: String,
val followerId: String,
val userId: String
) {
companion object {
val TABLE: Table<Record> = DSL.table("followers")
val ID: Field<String> = DSL.field("id", String::class.java)
val FOLLOWER_ID: Field<String> = DSL.field("follower_id", String::class.java)
val USER_ID: Field<String> = DSL.field("user_id", String::class.java)
}
}
When I did some trivial SQL statements and it worked perfectly, but when I tried the next statement, I'm getting exception.
return dsl.select().from(u.TABLE)
.rightJoin(f.TABLE).on(u.ID.eq(f.FOLLOWER_ID))
.where(u.ID.eq(id)).fetch().into(User::class.java)
The expected statement from this code is:
select *
from user u
right outer join followers f
on u.id = f.follower_id
where u.id = 'e30919bf-5f76-11e8-8c96-701ce7e27f83';
But the statement I got from this code is:
select *
from user
right outer join followers
on id = follower_id
where id = 'e30919bf-5f76-11e8-8c96-701ce7e27f83'
And of course, this givse me (rightfully) the error Column 'id' in where clause is ambiguous
It raises a few questions:
Is there a better way to declare table model without code generation.
Why the DSL select does not transform to proper SQL statement? What I'm doing wrong?
First off, some word of advice on your reluctance to use code generation:
i seems too complex, and eventually impossible to maintain.
so, i decided to create my own table models (similar to how hibernate works).
You're (probably) going down a long path of pain and suffering. First off, you will already now need to think of database migrations, which are best done using your database's DDL language. This means, your database model of your data should be more important to you in the long run, than your client model. In fact, your client model is a copy of your database model, not something you'd like to maintain independently. With this mindset, it is more reasonable to have a code generator generate your client model from the database model, not vice versa.
Sure, Hibernate makes the client first approach easy as well, when you start a project. Yet, once you go to production, you will have to migrate your database, and then this model will break. You're back to database first, and it's worth setting up everything already now.
So, no. Code generation might introduce some complexity now, but it will be much more easy to maintain down the road, than you creating your own table models.
I've written up a longer blog post about this topic, here.
Regarding your specific questions:
return dsl.select().from(u.TABLE)
.rightJoin(f.TABLE).on(u.ID.eq(f.FOLLOWER_ID))
.where(u.ID.eq(id)).fetch().into(User::class.java)
the expected statement from this code is: [...]
Well, that depends on what u and f are. You cannot just rename your Kotlin references to your table and expect jOOQ to know what they mean. I.e. you probably created the references as follows:
val u = User.TABLE;
val f = Follower.TABLE;
If that's how you created the reference, then the two things are the same thing by identity. jOOQ doesn't magically reverse engineer your Kotlin code to find out that you meant to alias your table. You have to tell jOOQ:
val u = User.TABLE.as("u");
val f = Follower.TABLE.as("f");
But now you're not done. You constructed the User.TABLE reference using the plain SQL API, which means that jOOQ's runtime has no idea about the columns in that table. You cannot reference those columns anymore from the aliased table, because the type of the aliased table for plain SQL tables is Table<?>, not User.
You could, of course, create TableImpl instances and register all columns inside of your TableImpl instance - just like the code generator does. In that case, you would have tables and columns associated with them, and could use them type safely even with aliased tables.
All of this stuff is handled automatically by generated code, which again, I recommend you use with jOOQ. The main reason why anyone would not use the code generator with jOOQ is because the data model is dynamic, i.e. not known at compile time. Otherwise, you're just going to repeat tons of work that the code generator already does for you, automatically. And, as mentioned before, you will have much more work later on, when you start migrating your schema.