Kotlin ktor exposed DSL insert with reference - kotlin

I am developing a Kotlin with ktor application and I am using exposed as ORM. I have a table with a reference to another table. The relationship is many to one. ex:
object Users : IdTable<String>() {
override val id: Column<EntityID<String>> = varchar("user_id", 64).entityId().primaryKey()
val email = varchar("email", 128).uniqueIndex()
val firstName = varchar("first_name", 64)
val lastName = varchar("last_name", 64)
}
& User Attendance table as -
object UserAttendances : IntIdTable() {
val userId = reference("user_id", Users).index()
val checkInTime = date("check_in")
val checkOutTime = date("check_out")
}
Now when I am trying to insert into the attendance table, i am not sure how to map the insert to users. I tried the following -
StaffAttendances.insert {
it[date] = DateTime.now()
it[checkInTime] = DateTime.now()
it[userId] = userId
}
This gives a compilation error that the required type is EntityId<String> but found String. Any help on how i can insert into a reference. This could possibly be because I have String as ID column with UUIDs

The userId must take an EntityId<String>. So you should pass your userId as it[userId] = EntityId(userId, Users).
To avoid this, I use plain Table instead of IntIdTable and manually create my primary key. This way I can pass directly the value without wrapping it in an EntityId<>.

Related

JPA using #ElementCollection with #OrderColumn but it makes exception to 'duplicate key value violates unique constraint'

First of all, I'm n.b to spring and jpa. so, Sorry for the rudimentary question.
These days I tried to make server system to location points storing using springboot + jpa + docker + postgresql /kotlin
my idea is server get client call and store locations periodically
so, I using #ElementCollection for store location item with #Embeddable
but, I got exception from springTest code
Hibernate:
insert
into
pos_info_pos_list
(pos_info_id, pos_list_order, accuracy, event_time, geo_lati, geo_long)
values
(?, ?, ?, ?, ?, ?)
2022-11-12 22:07:34.963 WARN 25880 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 0, SQLState: 23505
2022-11-12 22:07:34.963 ERROR 25880 --- [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : ERROR: duplicate key value violates unique constraint "pos_info_pos_list_pkey"
Detail: Key (pos_info_id, pos_list_order)=(1, 0) already exists.
I'll explain the table structure below
PosInfo(one), PosData(many)
oneToMany relation
I want to use ordercolumn for performance and want posList size limitation(MAX_POS_DATA_SIZE = 200)
#Entity
data class PosInfo(
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
var id: Long? = null
) {
#ElementCollection(fetch = FetchType.EAGER, targetClass = PosData::class)
#OrderColumn
val posList: MutableList<PosData> = mutableListOf()
fun addPosData(posData: PosDataDto) {
while (posList.size >= MAX_POS_DATA_SIZE) {
posList.removeFirst()
}
val newData = PosData(posData.geoLati, posData.geoLong, posData.eventTime, posData.accuracy)
posList.add(newData)
}
}
PosData table
#Embeddable
data class PosData(
#Column
val geoLati: String,
#Column
val geoLong: String,
#Column
val eventTime: Long,
#Column
val accuracy: Int,
)
SpringTestCode is
first of all, insert maxSize posData then add more one data again
#Test
fun addPathMax() {
val dummyPosData = PosDataDto("", "", System.currentTimeMillis(), 0)
val dummyPosData2 = PosDataDto("yyyy", "eeeee", System.currentTimeMillis(), 0)
val id = "KSH"
service.tryAddUser(id, "")
val userInfo = service.getUserInfo(id)
assertThat(userInfo).isNotNull
val posIndex = userInfo!!.posIndex
val posInfo = service.getPosInfo(posIndex)
assertThat(posInfo).isNotNull
for (i in 0 until MAX_POS_DATA_SIZE) {
posInfo!!.addPosData(dummyPosData)
}
service.updatePosInfo(posInfo!!)
println("Next Input Check KSH_TEST")
val posInfo2 = service.getPosInfo(posIndex)
posInfo2!!.addPosData(dummyPosData2)
service.updatePosInfo(posInfo2!!)
}
#Transactional
service.updatePosInfo <= it just call to crudRepository save method
but I got duplicate key again and again
Q1. Shouldn't the 'pos_list_order' be 'existing last +1' since the first data of the previous data was erased and the new data was inserted? why '0'?
// Key (pos_info_id, pos_list_order)=(1, 0) already exists.
Q2. Is this structure not good for updating and storing location data periodically?(using ElementCollection, should I use OneToMany?)
=To be honest, I've tried "one To Many" before. By the way, I gave up because I was tired of fixing strange build errors. I came back with "Element Collection," which I thought was easy
Thank you in advance for all the helpful comments
===========================
= I already tried before below
OneToMany with mapped, but it made many error and when I tried insert more value, it was made all delete row and re-install all and + newer again
ElementCollection looks simple, but it was made duplicated exception again and again
I already checked using below
#CollectionTable(
name = "pos_data",
joinColumns = [JoinColumn(name = "pos_info_id")]
)
JpaRepository.save then flush doesn't work
but same result, I don't know why.. really sad
I got a solution
Now this problem was caused by my poor understanding of 'Transactional'
it's fixed with below annotation
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Transactional(propagation = Propagation.REQUIRES_NEW)
#Rollback(false)
#Test
fun addPathMax() {
val dummyPosData = PosDataDto("", "", System.currentTimeMillis(), 0)
val dummyPosData2 = PosDataDto("yyyy", "eeeee", System.currentTimeMillis(), 0)
val id = "KSH"
service.tryAddUser(id, "")
val userInfo = service.getUserInfo(id)
assertThat(userInfo).isNotNull
val posIndex = userInfo!!.posIndex
val posInfo = service.getPosInfo(posIndex)
assertThat(posInfo).isNotNull
for (i in 0 until Constants.MAX_POS_DATA_SIZE) {
posInfo!!.addPosData(dummyPosData)
}
service.updatePosInfo(posInfo!!)
println("Next Input Check KSH_TEST")
val posInfo2 = service.getPosInfo(posIndex)
posInfo2!!.addPosData(dummyPosData2)
service.updatePosInfo(posInfo2!!)
}
I thought service already including 'Transactional' annotation
so it can be made query persist context to database
but it was not

Android Room Embedded Field Not Compiling

I am trying to create an embedded field. This is a simple example but I can't get this simple example to work. Eventually I need to have 3 levels of embedded items but trying to get this test case to work.
#Entity(tableName = "userItemsEntity")
#Parcelize
data class Item(
var objecttype: String?,
#PrimaryKey(autoGenerate = false)
var objectid: Int?,
var subtype: String?,
var collid: Int?,
#Embedded
var name: Name?
) : Parcelable
#Parcelize
data class Name(
var primary: Boolean? = true,
var sortindex: Int? = null,
var content: String? = null) : Parcelable
When I try and compile it it complains on the DAO that the updateItem()
SQL error or missing database (no such column: name)
DAO function
#Query("UPDATE userItemsEntity SET " +
"objecttype=:objecttype, objectid=:objectid, subtype=:subtype, collid=:collid, name=:name " +
"WHERE objectid=:objectid")
fun updateItem(
objecttype: String?,
objectid: Int,
subtype: String?,
collid: Int?,
name: Name?)
The reason is as it says there is no name column. Rather the table consists of the columns, as per the member variables of the EMBEDDED class (i.e. primary, sortindex and content).
i.e. the table create SQL is/will be :-
CREATE TABLE IF NOT EXISTS `userItemsEntity` (`objecttype` TEXT, `objectid` INTEGER, `subtype` TEXT, `collid` INTEGER, `primary` INTEGER, `sortindex` INTEGER, `content` TEXT, PRIMARY KEY(`objectid`))
Room knows to build the respective Name object from those columns when extracting rows.
So you could use :-
#Query("UPDATE userItemsEntity SET " +
"objecttype=:objecttype, objectid=:objectid, subtype=:subtype, collid=:collid, `primary`=:primary, sortindex=:sortindex, content=:content " +
"WHERE objectid=:objectid")
fun updateItem(
objecttype: String?,
objectid: Int,
subtype: String?,
collid: Int?,
primary: Boolean?,
sortindex: Int?,
content: String?
)
note that primary is an SQLite token and thus enclosed in grave accents to ensure that it is not treated as a token. Otherwise you would get :-
There is a problem with the query: [SQLITE_ERROR] SQL error or missing database (near "primary": syntax error)
However, as you are using a WHERE clause based upon the primary key (objectid) then the update will only apply to a single row and as such you can simply use:-
#Update
fun update(item: Item): Int
obviously the function's name need not be update it could be any valid name that suits.
this has the advantage of not only being simpler but of returning the number of rows updated (would be 1 if the row exists, otherwise 0)
Impementing a name column
If you want a name column and for that name column to hold a Name object. Then, as SQLite does not have storage/column types for objects then you would not EMBED the Name class.
You would have var name: Name? with an appropriate TypeConverter that would convert the Name object into a type that SQLite caters for :-
TEXT (String),
REAL (Float, Double...),
INTEGER (Long, Int ...) or
BLOB (ByteArray)).
Typically String is used and typically GSON is used to convert from an object to a JOSN String.
SQlite does have a NUMERIC type. However, Room doesn't support it's use. I believe because the other types cover all types of data and NUMERIC is a catch-all/default.
However, using a JSON representation of an object, introduces bloat and reduces the usefulness of the converted data from an SQL aspect.
For example say you had :-
#Entity(tableName = "userOtherItemsEntity")
#Parcelize
data class OtherItem (
var objecttype: String?,
#PrimaryKey(autoGenerate = false)
var objectid: Int?,
var subtype: String?,
var collid: Int?,
var name: OtherName?) : Parcelable
#Parcelize
data class OtherName(
var primary: Boolean? = true,
var sortindex: Int? = null,
var content: String? = null) : Parcelable
Then the underlying table does have the name column. The CREATE SQL, generated by Room, would be :-
CREATE TABLE IF NOT EXISTS `userOtherItemsEntity` (`objecttype` TEXT, `objectid` INTEGER, `subtype` TEXT, `collid` INTEGER, `name` TEXT, PRIMARY KEY(`objectid`))
However, you would need TypeConverters which could be :-
#TypeConverter
fun fromOtherName(othername: OtherName ): String {
return Gson().toJson(othername)
}
#TypeConverter
fun toOtherName(json: String): OtherName {
return Gson().fromJson(json,OtherName::class.java)
}
the first using Gson to convert the object to a JSON string, e.g. when inserting data
the second converts the JSON string to an OtherName object.
using Item with Name embedded then data would be stored along the lines of :-
Whilst with the OtherItem with OtherName being converted then the data (similar data) would be along the lines of :-
in the former the 3 Name columns would take up about (1 + 1 + 12) = 16 bytes.
in the latter, The OtherName columns (discounting the word Other whenever used) would take uo some 55 bytes.
the latter may require more complex and resource expensive searches if the components of the OtherName are to be included in searches.
e.g. #Query("SELECT * FROM userItemsEntity WHERE primary") as opposed to #Query("SELECT * FROM userOtherItemsEntity WHERE instr(name,'primary\":true') > 0")

InvalidDataAccessApiUsageException - Parameter value did not match expected type

I need to get all users with a specific role from a database. For this I need to use JPA.
All roles are stored in a special set:
UserAccount.kt
#ManyToMany(cascade = [(CascadeType.MERGE)])
#JoinTable(
name = "user_authorities",
joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")],
inverseJoinColumns = [JoinColumn(name = "authority_id", referencedColumnName = "id")]
)
var authoritySet: MutableSet<Authority> = hashSetOf()
I want to do it like this:
UserAccountService.kt
override fun getAllUsersByAuthorityName(name: String): List<UserAccountDto> {
return userAccountRepository.findUsersByAuthoritySet(mutableSetOf(authorityService.findAuthorityByAuthorityName(name))).map { it.toDto() }
}
UserAccountRepository.kt
#Query("select u from UserAccount u where u.authoritySet = ?1")
fun findUserAccountByAuthoritySet(authoritySet: MutableSet<Authority>): List<UserAccount>
But when calling these methods in tests, it gives an error:
Parameter value [Authority(id=1, authority='ROLE_ADMIN')] did not match expected type [java.util.Set (n/a)]; nested exception is java.lang.IllegalArgumentException: Parameter value [Authority(id=1, authority='ROLE_ADMIN')] did not match expected type [java.util.Set (n/a)]
Tell me how you can properly organize the search for users?
Can you show sites with examples of such requests?
First, I started using the JPA functionality and added the "In" particle to the method name in the repository.
In the second, I removed the #Query annotation
Third, I revisited my tests, and it turned out that I added roles after the saved users, and therefore my changes were not committed to the database. I fixed it.
Problem solved

Kotlin iterate a collection and map a certain value to new map

I have a map of collections . I need to get a list of ids from that..
val m1 = mapOf("id" to 1, "name" to "Alice")
val m2 = mapOf("id" to 2, "name" to "Bob")
val m3 = mapOf("id" to 3, "name" to "Tom")
val nameList = listOf(m1, m2, m3)
The result shall be [1, 2, 3]
Assuming you want a list as per the example, not a map as per the title, I would do it like this:
val result = nameList.map {
it.getValue("id").also { id ->
require(id is Int) { "id must be an Int" }
} as Int
}
This has the advantage of handling the following errors cleanly:
The id key is missing: NoSuchElementException: Key id is missing in the map
The id value is not an Int: IllegalArgumentException: id must be an Int
First things first, I believe that if you can, you should use classes instead of maps for storing heterogeneous data like this. So instead of your maps, you can use:
data class Person(val id: Int, val name: String)
val m1 = Person(id = 1, name = "Alice")
val m2 = Person(id = 2, name = "Bob")
val m3 = Person(id = 3, name = "Tom")
val list = listOf(m1, m2, m3)
val idsList = list.map { it.id } // no error handling required, rely on the type system
Now, if you really want to use maps like that, you have several options.
If you're certain the id key will be present and its value will be an Int, you can use the following:
nameList.map { it["id"] as Int }
This will fail with NullPointerException if id is not present in one of the maps or with ClassCastException if it's not an Int.
Normally you should make sure your map matches your contract at creation time, and not when accessing this kind of information.
But if you need to handle errors here for some reason, you can use the following instead:
nameList.map {
(it.getValue("id") as? Int) ?: error("'id' is not an Int")
}
getValue fails on absent keys with NoSuchElementException, and the error() call fails with IllegalStateException. You can also use other kinds of exceptions using throw or require().
If you want to just ignore the entries that don't have a valid integer id, you can use the following:
nameList.mapNotNull { it["id"] as? Int }
If you want to ignore the entries that don't have an id, but fail on those who have a non-integer id, you can use this:
nameList.mapNotNull { map ->
map["id"]?.let { id ->
(id as? Int) ?: error("'id' is not an Int")
}
}
These 2 last examples rely on mapNotNull, which filters the elements out if their mapped value is null.

Room Relation Specify Column Names When Extending

I am working with some datasets in my room database, and my method is to have one table with information about a dataset called DatasetInfo which stores things like name, type of value stored, id, etc; and a second table where I store the values in 3 columns: (id, date, value). This ordered triplet is defined as a DatasetValue entity. Here, (date, value) is an ordered pair that I want to plot.
To plot these ordered pairs, I have to convert them to a list of Entry objects, where Entry takes the values x and y. It makes the most sense to query my database and simply ask for List<Entry>, because right now I ask for List<DatasetValue> and then I have to map that result to List<Entry> which is unnecessary.
I query for the dataset information table DatasetInfo as follows:
data class DatasetWithValues(
#Embedded
var datasetInfo: DatasetInfo,
#Relation(
parentColumn = DATASET_COLUMN_DATASET_ID,
entityColumn = VALUES_COLUMN_ID,
entity = DatasetValue::class,
)
var values : List<Entry>
)
Now, as I said above, Entry has values x and y, and Dataset calls them date and value. Of course, when I ask for this relation, it will fail because it doesn't know how to assign values from a table with the columns id, date, and value to an object which takes x and y. So, I define a new class:
class DatasetEntry(
#ColumnInfo(name = "date")
var date : Float,
#ColumnInfo(name = "value")
val value : Float
) : Entry(date, value)
and then make the following adjustment:
//var values : List<Entry>
var values : List<DatasetEntry>
That does nothing. The code doesn't compile because:
SQL error or missing database (no such column: x)
Well, what if I instead write:
class DatasetEntry(
#ColumnInfo(name = "date")
var date : Float,
#ColumnInfo(name = "value")
val value : Float
) : Entry(){
init{
x = date
y = value
}
}
That doesn't help either, same error. Even if I remove that init call, it still wants x.
The plot thickens, because inside of Entry I can see x is declared private. So I have absolutely no clue what is happening here. How does Room even know to look for x? Is there any work around for this other than renaming the columns in my table to x and y?
Is there any work around for this other than renaming the columns in my table to x and y?
If you have such option it would be the easiest. Still there are some options you could consider:
1. Mapping Room's result to needed one
So, you ask Room for some raw result and then map it to ready one. For that you add 2 classes:
data class DatasetWithValuesRaw(
#Embedded
var datasetInfo: DatasetInfo,
#Relation(
parentColumn = DATASET_COLUMN_DATASET_ID,
entityColumn = VALUES_COLUMN_ID,
)
var values : List<DatasetValue>
)
data class DatasetWithValuesReady(
var datasetInfo: DatasetInfo,
var values : List<Entry>
)
Let's say you have a dao method:
Query("select * ....")
fun getRawData(): List<DatasetWithValuesRaw>
For mapping you use:
fun getReadyData() =
getRawData().map { item ->
DatasetWithValuesReady(item.datasetInfo,
item.values.map { Entry(x = it.date, y = it.value)
}) }
2. Replacing Room's #Relation with explicit query
It's not what you really want, but still is an option.
Use class like that:
data class DatasetWithSeparateValues(
#Embedded
var datasetInfo: DatasetInfo,
#Embedded
var value : Entry // <----- it's not a list, just a single value
)
and in your dao you set query with explicit columns' names (x and y). Something like that:
Query("SELECT *, values.date as x, values.value as y FROM dataset LEFT JOIN values on dataset.DATASET_COLUMN_DATASET_ID = values.VALUES_COLUMN_ID")
fun getData(): List<DatasetWithSeparateValues>
As a result you'll get a list, but if there is a one dataset with 5 values you'll get inside list 5 items with the same dataset and separate values. After that you could use Kotlin collection's methods (groupBy for example) to prettify result in some way