How to join three lists into a single Object - kotlin

How to transform three lists based on userId in a single object, where each object has the respected list object as a variable.
data class User(val id: String)
data class Book(val id: String, val userId: String)
data class Order(val id: String, val userId: String)
Input
val users: List<User> = listOf(
User(id = "u1"),
User(id = "u2"),
User(id = "u3")
)
val books: List<Book> = listOf(
Book(id = "b1.u1", userId = "u1"),
Book(id = "b2.u1", userId = "u1"),
Book(id = "b3.u2", userId = "u2"),
Book(id = "b4.ux", userId = "ux")
)
val order: List<Order> = listOf(
Order(id = "o1", userId = "u1"),
Order(id = "o2", userId = "u1"),
Order(id = "03", userId = "u2"),
Order(id = "o4", userId = "u1")
)
Output
val result = listOf(Result(user, book, order))

You can group the books and orders by userId and then for each user you can pick the corresponding books and orders.
val booksMap = books.groupBy { it.userId }
val ordersMap = orders.groupBy { it.userId }
users.map {
Result(
user = it,
books = booksMap[it.id] ?: emptyList(),
orders = ordersMap[it.id] ?: emptyList()
)
}
Here Result is:
data class Result(val user: User, val books: List<Book>, val orders: List<Order>)

Hard to get exactly what you need as output. To me, it makes the most sense to get a list of books and orders for each user. If that is the case, then you can do this:
data class Result(val user: User, val books: List<Book>, val orders: List<Order>)
val results = users.map { user ->
val userBooks = books.filter { it.userId == user.id }
val userOrders = orders.filter { it.userId == user.id }
Result(user, userBooks, userOrders)
}

Related

How to test a service function in kotlin which uses a late init Id?

I am new to testing in kotlin and I was wondering how I can test this function:
this is my SegmentService file:
fun createSegmentFromUserIds(userIds: List<String>, name:String, appId: String): Segmentation {
val filter = createUserIdFilter(userIds)
val createSegmentRequest = CreateSegmentRequest(
name = name, attribute = filter, type = SegmentType.STATIC
)
val segmentation = segmentMetaService.saveSegmentInfo(createSegmentRequest, appId)
querySegmentService.runUpdateQuery(users = userIds, appId = appId, segmentId = segmentation.id)
return segmentation
}
this is the saveSegmentInfo function:
fun saveSegmentInfo(createSegmentFilter: CreateSegmentRequest, appId: String): Segmentation {
val segmentInfo = segmentationRepository.save(createSegmentFilter.let {
Segmentation(
attribute = it.attribute, name = it.name, appId = appId, type = it.type
)
})
logger.info("Segment info saved with id: ${segmentInfo.id}, name: ${segmentInfo.name}")
return segmentInfo
}
and this is the Segmentation Document
#Document(Segmentation.COLLECTION_NAME)
class Segmentation(
#Field(ATTRIBUTE)
val attribute: Filter,
#Field(NAME)
val name: String,
#Indexed
#Field(APP_ID)
val appId: String,
#Field(STATUS)
var status: SegmentStatus = SegmentStatus.CREATED,
#Field(TYPE)
var type: SegmentType = SegmentType.STATIC,
#Field(USER_COUNT)
var userCount: Long? = null,
) {
#Id
lateinit var id: String
#Field(CREATION_DATE)
var creationDate: Date = Date()
}
I have written this test for it:
class SegmentServiceTest {
private val segmentMetaService = mock(SegmentMetaService::class.java)
private val querySegmentService = mock(QuerySegmentService::class.java)
private val converterService = mock(ConverterService::class.java)
private val userAttributeService = mock(UserAttributeService::class.java)
private val segmentConsumerUserInfoProducer = mock(SegmentConsumerUsersInfoProducer::class.java)
private val segmentationRepository = mock(SegmentationRepository::class.java)
#Test
fun `createSegmentFromUserIds should create a new segment`() {
val segmentService = SegmentService(
segmentMetaService = segmentMetaService,
querySegmentService = querySegmentService,
converterService = converterService,
userAttributeService = userAttributeService,
segmentConsumerUserInfoProducer = segmentConsumerUserInfoProducer
)
val userIds = listOf("user-1", "user-2", "user-3")
val filter = AndFilter(
operations = listOf(
AndFilter(
operations = listOf(
StringListContainsFilter(
field = "userId", operationType = StringQueryOperationType.IN, values = userIds
)
)
)
)
)
val createSegmentRequest = CreateSegmentRequest(
name = "segment-name", attribute = filter, type = SegmentType.STATIC
)
val segment = Segmentation(attribute = filter, name = "segment-name", type = SegmentType.STATIC, appId = "app-id" )
`when`(segmentationRepository.save(any())).thenReturn(segment)
`when`(segmentMetaService.saveSegmentInfo(createSegmentRequest, "app-id")).thenReturn(segment)
val createdSegment = segmentService.createSegmentFromUserIds(userIds = userIds, name = "segment-name", appId = "app-id")
assertEquals(segment, createdSegment)
}
}
but I am facing this error:
kotlin.UninitializedPropertyAccessException: lateinit property id has not been initialized
So what am I doing wrong here?
How can I initialize the Id? or should I refactor my code so for it to become testable?
I think you are mocking / providing the answers to the wrong calls.
you mock segmentationRepository.save(..) but this call won't ever be made since that call is inside segmentMetaService.saveSegmentInfo(...) which is mocked so the real code is not called.
when you call segmentService.createSegmentFromUserIds(..), my guess (stack trace would be useful on the error, BTW), is that this method proceeds passed the invocation of segmentMetaService.saveSegmentInfo(...) but in the next line you call querySegmentService.runUpdateQuery(users = userIds, appId = appId, segmentId = segmentation.id) which is accessing the uninitialised id.
The fix will be to set the segment.id when you set up the object for the answer to segmentMetaService.saveSegmentInfo(..)

FOREIGN KEY constraint failed (code 787 SQLITE_CONSTRAINT_FOREIGNKEY) when add data to table room

I have to tables CheckListModel and CheckListPoints, it is one to n relationship, when i try to add
data in DB CheckListModel adds correctly, but when code gose to add CheckListPoints i got this error. I have no idea why this happening
This is my DB
#Database(entities = [CheckListModel::class,CheckListPoints::class],version = 4,exportSchema = false)
abstract class CheckListDB : RoomDatabase() {
abstract fun checkListDBDao():CheckListModelDBDao
companion object {
#Volatile
private var instance: CheckListDB? = null
fun getInstance(context: Context):CheckListDB{
return instance ?: synchronized(this){
instance?: buildDatabase(context).also { instance = it }
}
}
private fun buildDatabase(context: Context): CheckListDB{
return Room.databaseBuilder(context,CheckListDB::class.java,"check_list_model").fallbackToDestructiveMigration().build()
}
}
}
entityes
#Entity(
tableName = "check_list_point",
foreignKeys = [
ForeignKey(entity = CheckListModel::class, parentColumns = ["checkListModelID"],childColumns = ["checkListColumnID"],onDelete = ForeignKey.CASCADE)
],
indices = [Index("checkListColumnID")]
)
data class CheckListPoints(
#ColumnInfo(name = "correctly")
var correctly: Boolean,
#ColumnInfo(name = "requirement")
var requirement: String,
#ColumnInfo(name = "passed")
var passed: Boolean,
#ColumnInfo(name="checkListColumnID")
val checkListColumnID: Long,
#PrimaryKey(autoGenerate = true)
val checkListPointsModelID: Long = 0L
): Serializable
#Entity(tableName = "check_list_model")
data class CheckListModel (
#ColumnInfo(name = "check_list_name")
val checkListName: String,
#ColumnInfo(name = "check_list_count")
val checkListCount: Int,
#ColumnInfo(name = "check_list_result")
val checkListResult: Int,
#ColumnInfo(name = "description")
val description: String,
#PrimaryKey(autoGenerate = true)
val checkListModelID: Long = 0L
) : Serializable
relationship
data class CheckListWithCheckListModel(
#Embedded val CheckList: CheckListModel,
#Relation(
parentColumn = "checkListModelID",
entityColumn = "checkListColumnID"
)
val checkListPoints: List<CheckListPoints>
)
this is dao
#Dao
interface CheckListModelDBDao {
#Insert
fun insertCheckList(data:CheckListModel)
#Insert
fun insertCheckListPoint(vararg data:CheckListPoints)
#Delete
fun deleteCheckList(checkList: CheckListModel)
#Transaction
#Query("SELECT * FROM check_list_model " )
fun getEverything(): Flow<List<CheckListWithCheckListModel>>
}
and this is how i add
private var doorCheckListModel = CheckListModel("Дверь",0,0,"4321")
private val doorCheckListPoint1 = CheckListPoints(false,"1",false,doorCheckListModel.checkListModelID)
private val doorCheckListPoint2 = CheckListPoints(false,"2",false,doorCheckListModel.checkListModelID)
private var doorListOfCheckListPoints = listOf<CheckListPoints>(doorCheckListPoint1,doorCheckListPoint2)
private var windowCheckListModel = CheckListModel("Окно",0,0,"4321")
private var windowCheckListPoint1 = CheckListPoints(false,"1",false,windowCheckListModel.checkListModelID)
private var windowCheckListPoint2 = CheckListPoints(false,"1",false,windowCheckListModel.checkListModelID)
private var windowListOfCheckListPoints = listOf<CheckListPoints>(windowCheckListPoint1,windowCheckListPoint2)
var checkLists = MutableLiveData<List<CheckListModel>>().apply {
value = listOf(doorCheckListModel,windowCheckListModel)
}
fun addCheckList(name: String){
viewModelScope.launch(Dispatchers.IO) {
when (name) {
"Дверь" -> insert(doorCheckListModel,doorListOfCheckListPoints)
"Окно" -> insert(windowCheckListModel,windowListOfCheckListPoints)
}
}
}
private suspend fun insert(checkList: CheckListModel, checkListPoints: List<CheckListPoints>){
database.insertCheckList(checkList)
for(checkListPoint in checkListPoints){
database.insertCheckListPoint(checkListPoint)
}
}
}
also i display data from CheckListModel in fragment. CheckListModel added to DB Correctly and display correctly, but CheckListPoints has not
When you create doorCheckListModel, its checkListModelID is initially 0. You use this 0 as checkListColumnID in doorCheckListPoint1. So when you save the CheckListModel, Room automatically generates the primary key and saves in the table. Similar is the case for primary key in CheckListPoints table. But the entries saved in CheckListPoints table still have 0 in checkListColumnID column.
This is why the foreign key constraint is failing. There is no CheckListModel with 0 as its primary key. To fix this, you will have to set the value of checkListColumnID before saving a CheckListPoints entry in the table.
If you go through Room documentation, the #Insert annotated function can optionally return the rowId for the inserted item. For integer primary keys, rowId is the same as primary key.
Try this code:
// Return the primary key here
#Insert
fun insertCheckList(data:CheckListModel): Long
private suspend fun insert(checkList: CheckListModel, checkListPoints: List<CheckListPoints>){
val id = database.insertCheckList(checkList)
for(checkListPoint in checkListPoints){
database.insertCheckListPoint(checkListPoint.copy(checkListColumnID = id))
}
}

Room How to fetch on 3 tables with Foreign Keys

I have a table named User. This table has two foreign keys Account and Shop (both 1..1 relationship).
So I've created a data class called UserAndAccount and another called UserAndShop.
How can I now fetch all this data (would be equal to UserAndAccountAndShop) ?
User.kt
#Entity(
tableName = "User"
)
data class User(
#PrimaryKey(autoGenerate = false)
#SerializedName("unique_id")
var uniqueID: String,
#SerializedName("email")
var email: String = "",
#SerializedName("username")
var username: String = ""
)
Account.kt
#Entity(
tableName = "Account"
)
data class Account(
#PrimaryKey(autoGenerate = true)
#SerializedName("account_id")
var accountID: Int,
#SerializedName("user_id")
var userOwnerID: String = "",
#SerializedName("token")
var token: String = ""
)
Shop.kt
#Entity(
tableName = "Shop"
)
data class Shop(
#PrimaryKey(autoGenerate = true)
#SerializedName("shop_id")
var shopID: Int,
#SerializedName("shop_owner_id")
var shopOwnerID: String,
#SerializedName("shop_name")
var shopName: String = ""
)
UserAndAccount.kt
data class UserAndAccount(
#Embedded val user: User,
#Relation(
parentColumn = "uniqueID",
entityColumn = "userOwnerID"
)
val account: Account
)
UserAndShop.kt
data class UserAndShop(
#Embedded val user: User,
#Relation(
parentColumn = "uniqueID",
entityColumn = "shopOwnerID"
)
val shop: Shop
)
To anyone looking for an answer:
You can add multiples #Relation into a data class.
So I've created this data class:
UserAndAccountAndShop.kt
data class UserAndAccountAndShop(
#Embedded val user: User,
#Relation(
parentColumn = "uniqueID",
entityColumn = "accountOwnerID"
)
val account: Account,
#Relation(
parentColumn = "uniqueID",
entityColumn = "shopOwnerID"
)
val shop: Shop
)
And that's it!

Is it possible to make select operation without JOIN command by using Spring Data JPA?

I have three Entities: User, UserFile and UserSearchInfo. User entity has a one-to-one relationship to UserFile entity and one-to-one relationship to UserSearchInfo entity.
User entity:
#Entity
#Table(name = "users")
data class User(
#Id
val id: UUID,
val username: String?,
val age: Int,
#Enumerated(EnumType.STRING)
val lang: Language?,
#Enumerated(EnumType.STRING)
val city: City?,
#Enumerated(EnumType.STRING)
val genderType: GenderType,
val createdAt: LocalDateTime,
val updatedAt: LocalDateTime
) {
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
val userSearchInfo: UserSearchInfo? = null
#OneToOne(fetch = FetchType.LAZY, mappedBy = "user")
val userFile: UserFile? = null
}
UserFile entity:
#Entity
#Table(name = "users_file")
data class UserFile(
#Id
val id: UUID,
val fileId: String,
val fileName: String,
val mimeType: String,
val fileSize: Int,
val createdAt: LocalDateTime,
val updatedAt: LocalDateTime
) {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", nullable = false)
val user: User? = null
}
UserSearchInfo entity:
#Entity
#Table(name = "users_search_info")
data class UserSearchInfo(
#Id
val id: UUID,
val ageFrom: Int,
val ageTo: Int,
#Enumerated(EnumType.STRING)
val city: City?,
#Enumerated(EnumType.STRING)
val genderType: GenderType,
val createdAt: LocalDateTime,
val updatedAt: LocalDateTime
) {
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "id", nullable = false)
val user: User? = null
}
I want to make this SQL operation:
SELECT *
FROM users
LEFT JOIN users_file as userFile
ON users.id = userFile.id
JOIN users_search_info as userSearchInfo
ON users.id = userSearchInfo.id
WHERE users.id != :userId AND age >= :ageStart AND age <= :ageEnd
AND users.gender_type = :genderType AND userSearchInfo.gender_type = :wantedGenderType
ORDER BY CASE WHEN lang = :firstLang then '1'
WHEN lang = :secondLang then '2'
ELSE lang END,
userFile ASC NULLS LAST;
I did it by using JOIN command:
interface UserRepository : CrudRepository<User, UUID> {
#Query("""
SELECT user
FROM User user
LEFT JOIN UserFile userFile
ON user.id = userFile.id
JOIN UserSearchInfo userSearchInfo
ON user.id = userSearchInfo.id
WHERE user.id != :userId AND user.age >= :ageStart AND user.age <= :ageEnd
AND user.genderType = :genderType AND userSearchInfo.genderType = :wantedGenderType
ORDER BY CASE WHEN user.lang = :firstLang then '1'
WHEN user.lang = :secondLang then '2'
ELSE user.lang END,
userFile ASC NULLS LAST
""")
fun findUsers(
#Param("userId") userId: UUID,
#Param("firstLang") firstLang: Language,
#Param("secondLang") secondLang: Language,
#Param("ageStart") ageStart: Int,
#Param("ageEnd") ageEnd: Int,
#Param("genderType") genderType: GenderType,
#Param("wantedGenderType") wantedGenderType: GenderType
): List<User>
}
Also, I tried to do without JOIN command. But it doesn't work:
interface UserRepository : CrudRepository<User, UUID> {
#Query("""
SELECT user
FROM User user
WHERE user.id != :userId AND user.age >= :ageStart AND user.age <= :ageEnd
AND user.genderType = :genderType AND user.userSearchInfo.genderType = :wantedGenderType
ORDER BY CASE WHEN user.lang = :firstLang then '1'
WHEN user.lang = :secondLang then '2'
ELSE user.lang END,
user.userFile ASC NULLS LAST
""")
fun findUsers(
#Param("userId") userId: UUID,
#Param("firstLang") firstLang: Language,
#Param("secondLang") secondLang: Language,
#Param("ageStart") ageStart: Int,
#Param("ageEnd") ageEnd: Int,
#Param("genderType") genderType: GenderType,
#Param("wantedGenderType") wantedGenderType: GenderType
): List<User>
}
You don't need to use left join or join inside query because JPQL do this job. in your case try this:
interface UserRepository : JpaRepository<User, UUID> {
#Query("select u,(case
when (u.lang = :firstLang) then 1
when (u.lang = :secondLang) then 2
else 3 END) as myLang from User as u where u.id=:userId and u.age >= :ageStart and u.age<=:ageEnd and u.genderType=:genderType and u.userSearchInfo.genderType=:wantedGenderType order by myLang,u.userFile asc nulls last")
fun findUsers #Param("userId") userId: UUID,
#Param("firstLang") firstLang: Language,
#Param("secondLang") secondLang: Language,
#Param("ageStart") ageStart: Int,
#Param("ageEnd") ageEnd: Int,
#Param("genderType") genderType: GenderType,
#Param("wantedGenderType") wantedGenderType: GenderType): List<User>
}

CRUD with table relationship using KTOR and EXPOSED

I'm having problems with KTOR and EXPOSED for a crud using relationship between tables.
I configured my service as follows:
class LegalPersonService {
suspend fun findAll(): List<LegalPerson> = dbQuery {
LegalPersons.selectAll().map { toLp(it) }
}
suspend fun insert(lp: LegalPerson, ph: Phone) = dbQuery {
LegalPersons.insert {
it[id] = lp.id
it[internalId] = lp.internalId
it[companyId] = lp.companyId
it[active] = lp.active
it[tradeName] = lp.tradeName
it[fantasyName] = lp.fantasyName
it[email] = lp.fantasyName
it[cnpj] = lp.cnpj
it[stateRegistration] = lp.stateRegistration
it[muninipalRegistration] = lp.muninipalRegistration
it[address] = lp.address
}.let {
Phones.insert {
it[id] = ph.id
it[internalId] = ph.internalId
it[phone] = ph.phone
}
}
}
private fun toLp(row: ResultRow): LegalPerson =
Phone(
id = row[Phones.id],
internalId = row[Phones.internalId],
phone = row[Phones.phone]
).let {
LegalPerson(
id = row[LegalPersons.id],
internalId = row[LegalPersons.internalId],
companyId = row[LegalPersons.companyId],
active = row[LegalPersons.active],
tradeName = row[LegalPersons.tradeName],
fantasyName = row[LegalPersons.fantasyName],
email = row[LegalPersons.email],
cnpj = row[LegalPersons.cnpj],
stateRegistration = row[LegalPersons.stateRegistration],
muninipalRegistration = row[LegalPersons.muninipalRegistration],
address = row[LegalPersons.address]
)
}
}
And my model:
// *** LEGAL PERSONS ***
data class LegalPerson(
val id: UUID,
val internalId: Long,
val companyId: UUID,
val active: Boolean,
val tradeName: String,
val fantasyName: String,
val email: String,
val cnpj: String,
val stateRegistration: String,
val muninipalRegistration: String,
val address: UUID
)
object LegalPersons : Table("person.legal_person") {
val id: Column<UUID> = uuid("id").autoIncrement().primaryKey()
val internalId: Column<Long> = long("internal_id").autoIncrement()
val companyId: Column<UUID> = uuid("company_id")
val active: Column<Boolean> = bool("active")
val tradeName: Column<String> = varchar("trade_name", 100)
val fantasyName: Column<String> = varchar("fantasy_name", 100)
val email: Column<String> = varchar("email", 100)
val cnpj: Column<String> = varchar("cnpj", 18)
val stateRegistration: Column<String> = varchar("state_registration", 20)
val muninipalRegistration: Column<String> = varchar("municipal_registration", 20)
val address: Column<UUID> = uuid("address")
}
// *** PHONES ***
data class Phone(
val id: UUID,
val internalId: Long,
val phone: UUID
)
object Phones : Table("person.phone_legal_person") {
val id: Column<UUID> = reference("id", LegalPersons.id).primaryKey()
val internalId: Column<Long> = long("internal_id").autoIncrement()
val phone: Column<UUID> = uuid("phone")
}
But I'm getting this error when I'm trying to insert data:
ERROR Application - Unhandled: POST - /api/clients/lp/
io.ktor.gson.UnsupportedNullValuesException: Receiving null values is not supported
Could anyone help? I am using DLS as opposed to DAO.
I am having difficulty, since the documentation is still being made.
After reading a little I found out what was wrong.
My service looks like this:
suspend fun insert(lp: LegalPerson) = dbQuery {
val ids = UUID.randomUUID()
LegalPersons.insert {
it[id] = ids
it[companyId] = lp.companyId
it[active] = lp.active
it[tradeName] = lp.tradeName
it[fantasyName] = lp.fantasyName
it[email] = lp.fantasyName
it[cnpj] = lp.cnpj
it[stateRegistration] = lp.stateRegistration
it[muninipalRegistration] = lp.muninipalRegistration
it[address] = lp.address
}
Phones.batchInsert(lp.phones) { phone ->
this[Phones.id] = ids
this[Phones.phone] = phone.phone
}
}