Kotlin Exposed ORM - id not in record set - kotlin

New to Kotlin Exposed and trying to store data using IdTable and getting errror 'id is not in record set.' Below is the code using
object GeoLocationModel : IdTable<Long>("geolocation_pos") {
val code = long("code").primaryKey()
val createdAt = datetime("created_at").clientDefault { currentUtc() }
val updatedAt = datetime("updated_at").nullable()
val createdBy = varchar("created_by", 255).clientDefault { authenticatedUser() }
val updatedBy = varchar("updated_by", 255).nullable()
val uuid = uuid("uuid").autoGenerate().uniqueIndex()
val latitude: Column<Double> = double("latitude")
val longitude: Column<Double> = double("longitude")
val pincode: Column<Long> = long("pincode")
override val id: Column<EntityID<Long>> = code.entityId()
}
object GeoLocationModel : IdTable<Long>("geolocation_pos") {
val code = long("code").primaryKey()
val createdAt = datetime("created_at").clientDefault { currentUtc() }
val updatedAt = datetime("updated_at").nullable()
val createdBy = varchar("created_by", 255).clientDefault { authenticatedUser() }
val updatedBy = varchar("updated_by", 255).nullable()
val uuid = uuid("uuid").autoGenerate().uniqueIndex()
val latitude: Column<Double> = double("latitude")
val longitude: Column<Double> = double("longitude")
val pincode: Column<Long> = long("pincode")
override val id: Column<EntityID<Long>> = code.entityId()
}
Insertion of data for **geolocation_pos** table
val geolocationId = GeoLocationModel.insert {
it[code] = nextSequenceVal()
it[latitude] = 28.550667
it[longitude] = 77.268952
it[pincode] = xxxxxx
} get GeoLocationModel.id
MySQL database is not able to save throws an error stating
java.lang.IllegalStateException: com.xxxx.xxxx.xxxx.xxx.GeoLocationModel.code is not in record set
at org.jetbrains.exposed.sql.ResultRow.getRaw(ResultRow.kt:53) ~[exposed-core-0.21.1.jar:na]
at org.jetbrains.exposed.sql.ResultRow.get(ResultRow.kt:18) ~[exposed-core-0.21.1.jar:na]
at org.jetbrains.exposed.sql.statements.InsertStatement.get(InsertStatement.kt:20) ~[exposed-core-0.21.1.jar:na]
Generated SQL
INSERT INTO geolocation_pos (code, created_at, created_by, latitude, longitude, pincode, updated_at, updated_by, uuid) VALUES (106, 'xxxxxxx', 'xxx', 28.550667, 77.268952, xxxxx, NULL, NULL, 'dd6aaf69-5324-4fac-9271-5c099957e06b')
Please can anyone help me resolving the issue.

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(..)

How to restrict Int values bound to database column?

data class:
// Entity of query
#Entity(tableName = TABLE_NAME)
data class HistoryItem(
#PrimaryKey(autoGenerate = true)
val id: Int,
#ColumnInfo(name = SEARCHED_DOMAIN)
val searchedDomain: String,
#ColumnInfo(name = STATUS)
val status: Int,
)
And object of statuses:
object Statuses {
const val FAILURE = 0
const val NOT_FOUND = 1
const val FOUND = 2
}
How to make val status: Int to always be FAILURE or NOT_FOUND or FOUND? I think it should looks like this:
#Status
#ColumnInfo(name = STATUS)
val status: Int
But how to do it?
I would recommend using an enum class for this:
enum class Status {
FAILURE, NOT_FOUND, FOUND;
}
#Entity(tableName = TABLE_NAME)
data class HistoryItem(
#PrimaryKey(autoGenerate = true)
val id: Int,
#ColumnInfo(name = SEARCHED_DOMAIN)
val searchedDomain: String,
#ColumnInfo(name = STATUS)
val status: Status
)
However, older versions of Android Room (prior to 2.3.0) do not automatically convert enum classes, so if you're using these you will need to use a type convertor:
class Converters {
#TypeConverter
fun toStatus(value: Int) = enumValues<Status>()[value]
#TypeConverter
fun fromStatus(value: Status) = value.ordinal
}
Which requires you to add the following to your database definition:
#TypeConverters(Converters::class)
See also this answer.

Get updated id from entity autogenerate primary key

I created Alarm class and annotate it as Entity on Android Studio.
In this class I have put id variable as Entity Primary Key and it is auto generate.
#Entity(tableName = "alarm_data_table")
class Alarm(
#ColumnInfo(name = "alarm_hour")
var alarmHour: Int,
#ColumnInfo(name = "alarm_min")
var alarmMin: Int,
#ColumnInfo(name = "is_am")
var isAM: Boolean,
var days: ArrayList<Int>?,
#ColumnInfo(name = "is_on")
var isOn: Boolean,
var label: String,
#PrimaryKey(autoGenerate = true)
val id: Int = 0
) {
fun setAlarmSchOnOff(isOn: Boolean, activity: Activity){
if (isOn){setAlarmScheduleOn(activity)}
if (!isOn){setAlarmScheduleOff(activity)}
}
fun setAlarmScheduleOn (activity: Activity) {
val alarmManager = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
Log.d("ALARM ON", Integer.toString(id))
val alarmIntent = Intent(activity, AlarmReceiver::class.java).let { intent ->
intent.putExtra(Constants().ALARM_LABEL_KEY,label)
PendingIntent.getBroadcast(
activity,
id,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)
}
val calendar: Calendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY, alarmHour)
set(Calendar.MINUTE, alarmMin)
}
days?.let {
it.forEach {
calendar.set(Calendar.DAY_OF_WEEK, it) }
alarmManager.setInexactRepeating(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
AlarmManager.INTERVAL_HOUR,
alarmIntent
)
} ?:run{
alarmManager.set(
AlarmManager.RTC_WAKEUP,
calendar.timeInMillis,
alarmIntent)
}
}
fun setAlarmScheduleOff(activity: Activity) {
val alarmManager = activity.getSystemService(Context.ALARM_SERVICE) as AlarmManager
Log.d("ALARM OFF", Integer.toString(alarmId))
val alarmIntent = Intent(activity, AlarmReceiver::class.java).let { intent ->
PendingIntent.getBroadcast(
activity,
id,
intent,
PendingIntent.FLAG_UPDATE_CURRENT
)}
alarmManager.cancel(alarmIntent)
}
}
The problem is every time I tried to retrieve id for setAlarm method it will always return 0.
2019-10-30 15:38:07.441 11066-11066/com.andreasgift.myclock D/ALARMĀ ON: 0
Is there any way to return the id value after Entity update the value/ successfully insert it into table.
Is there any way to return the id value after Entity update the value/
successfully insert it into table
Change to use (optional)
:-
#PrimaryKey(autoGenerate = true)
var id: Long = 0 /*<<<<<<<<<< var not val + Long instead of Int */
you should really use Long for id columns as
a) the SQliteDatabase insert method that is ultimately used, returns a long and
b) (which is why a is as it is)) a rowid (which is effectively what autogenerate uses ) can be a value larger then an Int can hold (i.e. a 64 bit signed integer).
If you wanted to use Int for id then you could use myAlarm.id = (alarmDao.insertAlarm(myAlarm)).toInt() instead of myAlarm.id = alarmDao.insertAlarm(myAlarm)
And use a Dao
#Insert
fun insertAlarm(alarm :Alarm) :Long
i.e. set it so that it returns :Long
The insert now returns the id (or -1 if the insert didn't insert the row). If you still use the Alarm object after the insert then you could use something like :-
myAlarm.id = alarmDao.insertAlarm(myAlarm) /*<<<<<<<<<< SET THE id according to the inserted id */

How to find the updated fields between a payload and an entity fetched from DB and create an object having fields with updated values and rest Null

Given an update request for a record in DB, I have to find a difference between the payload and existing data in DB then create a new Object which has updated fields with Payload values and rest as Null.
I have created a function which gives me a list of field names which were updated, But I'm unable to create a new object which has values for only these updated fields.The problem is that the function uses "field: Field in cpayload.javaClass.declaredFields" which is kind of generic so I'm unable to set these fields.
fun findupdatedFieldsList(cpayload: Customer, cEntity: Customer): List<String> {
// var customerToPublish = Customer()
val updatedFieldsList: MutableList<String>
updatedFieldsList = ArrayList()
for (field: Field in cpayload.javaClass.declaredFields) {
field.isAccessible = true
val value1 = field.get(cpayload).toString()
val value2 = field.get(cEntity).toString()
!Objects.equals(value1, value2).apply {
if (this) {
// customerToPublish.birthDate=field.get(cpayload).toString()
updatedFieldsList.add(field.name)
}
}
}
return updatedFieldsList
}
#Entity
#Table
data class Customer(
#Id
val partyKey: UUID,
var preferredName: String?,
var givenName: String?,
var lastName: String?,
var middleName: String?,
var emailAddress: String,
var mobileNumber: String,
val birthDate: String?,
val loginOnRegister: Boolean,
var gender: Gender?,
var placeOfBirth: String?,
var createdDate: LocalDateTime = LocalDateTime.now(),
var updatedDate: LocalDateTime = LocalDateTime.now()
)
Desired Output
val customer = Customer(
preferredName = Updated name,
partyKey = partyKey.value,
givenName = Updated name,
lastName = null,
middleName = null,
emailAddress = Updated email,
mobileNumber = null,
birthDate = null,
gender = null,
placeOfBirth = null
)
I was able to construct a solution using Kotlin's reflect. It is generic and can be applied to any Kotlin class that have primary constructor. Unfortunately it won't work with Java classes
You would need to add kotlin-reflect package to your build tool config, e.g. for Gradle:
implementation 'org.jetbrains.kotlin:kotlin-reflect:XXXXXX'
First we will build a function to extract updated properties. Please take a note that we also need to extract properties that are mandatory (non-nullable and without default). We add them to a map of propertyName -> propertyValue:
fun Map<String?, KParameter>.isOptional(name: String) = this[name]?.isOptional ?: false
fun <T : Any> findUpdatedProperties(payload: T, entity: T): Map<String, Any?> {
val ctorParams = payload::class.primaryConstructor!!.parameters.associateBy { it.name }
return payload::class.memberProperties.map { property ->
val payloadValue = property.call(payload)
val entityValue = property.call(entity)
if (!Objects.equals(payloadValue, entityValue) || (!ctorParams.isOptional(property.name))) {
property.name to payloadValue
} else {
null
}
}
.filterNotNull()
.toMap()
}
Then we call this function and construct a new instance of provided class:
fun <T : Any> constructCustomerDiff(clazz: KClass<T>, payload: T, entity: T): T {
val ctor = clazz.primaryConstructor!!
val params = ctor.parameters
val updatedProperties = findUpdatedProperties(payload, entity)
val values = params.map { it to updatedProperties[it.name] }.toMap()
return ctor.callBy(values)
}
Take a note that missing primary constructor will throw NullPointerException because of use of !!.
We could call this funcion as constructCustomerDiff(Customer::class, payload, entity), but we can do better with reified types:
inline fun <reified T : Any> constructCustomerDiff(payload: T, entity: T): T {
return constructCustomerDiff(T::class, payload, entity)
}
Now we can use this function in convenient Kotlin style:
val id = UUID.randomUUID()
val payload = Customer(
partyKey = id,
preferredName = "newName",
givenName = "givenName"
)
val entity = Customer(
partyKey = id,
preferredName = "oldName",
givenName = "givenName" // this is the same as in payload
)
val x = constructCustomerDiff(payload, entity)
assert(x.partyKey == id && x.givenName == null || x.preferredName == "newName")

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
}
}