Skip to content

Commit

Permalink
feat: add update functions to dao API
Browse files Browse the repository at this point in the history
  • Loading branch information
Reid Buzby committed Jun 29, 2023
1 parent 22b4002 commit af3f478
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 0 deletions.
22 changes: 22 additions & 0 deletions documentation-website/Writerside/topics/DAO-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,28 @@ Update a value of a property similar to any property in a Kotlin class:
movie.name = "Episode VIII – The Last Jedi"
```
* Note: Exposed doesn't make an immediate update when you set a new value for Entity, it just stores it on the inner map. "Flushing" values to the database occurs at the end of the transaction or before next `select *` from the database.

Search for an entity by its id and apply an update:
```kotlin
val updatedMovie = StarWarsFilm.findByIdAndUpdate(5) {
it.name = "Episode VIII – The Last Jedi"
}
```

Search for a single entity by a query and apply an update:
```kotlin
val updatedMovie2 = StarWarsFilm.findSingleByAndUpdate(StarWarsFilms.name eq "The Last Jedi") {
it.name = "Episode VIII – The Last Jedi"
}
```

Search for multiple entities by a query and apply an update to all entities:
```kotlin
val updatedMovies = StarWarsFilm.findManyByAndUpdate(StarWarsFilms.director eq "George Lucas") {
it.director = "Lucas, George"
}
```

### Delete
```kotlin
movie.delete()
Expand Down
3 changes: 3 additions & 0 deletions exposed-dao/api/exposed-dao.api
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ public abstract class org/jetbrains/exposed/dao/EntityClass {
public final fun find (Lorg/jetbrains/exposed/sql/Op;)Lorg/jetbrains/exposed/sql/SizedIterable;
public final fun findById (Ljava/lang/Comparable;)Lorg/jetbrains/exposed/dao/Entity;
public fun findById (Lorg/jetbrains/exposed/dao/id/EntityID;)Lorg/jetbrains/exposed/dao/Entity;
public final fun findByIdAndUpdate (Ljava/lang/Comparable;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/dao/Entity;
public final fun findManyByAndUpdate (Lorg/jetbrains/exposed/sql/Op;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/sql/SizedIterable;
public final fun findSingleByAndUpdate (Lorg/jetbrains/exposed/sql/Op;Lkotlin/jvm/functions/Function1;)Lorg/jetbrains/exposed/dao/Entity;
public final fun findWithCacheCondition (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/sequences/Sequence;
public fun forEntityIds (Ljava/util/List;)Lorg/jetbrains/exposed/sql/SizedIterable;
public final fun forIds (Ljava/util/List;)Lorg/jetbrains/exposed/sql/SizedIterable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import org.jetbrains.exposed.dao.exceptions.EntityNotFoundException
import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq
import org.jetbrains.exposed.sql.transactions.TransactionManager
import java.util.concurrent.ConcurrentHashMap
import kotlin.properties.ReadOnlyProperty
Expand Down Expand Up @@ -46,6 +47,36 @@ abstract class EntityClass<ID : Comparable<ID>, out T : Entity<ID>>(
*/
fun findById(id: ID): T? = findById(DaoEntityID(id, table))

/**
* Get an entity by its [id] and update the entity.
*
* @param id The id of the entity
* @param block Lambda that contains entity updates
*
* @return The updated entity that has this id or null if no entity was found.
*/
fun findByIdAndUpdate(id: ID, block: (it: T) -> Unit): T? = find(table.id eq id).forUpdate().singleOrNull()?.apply { block(this@apply) }?.also { invalidateEntityInCache(it) }

/**
* Find a single entity that conforms to the [op] statement.
*
* @param op The statement to select the entity for. The statement must be of boolean type.
* @param block Lambda that contains entity updates
*
* @return The updated entity that conforms to this op or null if no entity was found.
*/
fun findSingleByAndUpdate(op: Op<Boolean>, block: (it: T) -> Unit): T? = find(op).forUpdate().singleOrNull()?.apply { block(this) }?.also { invalidateEntityInCache(it) }

/**
* Find many entities that conform to the [op] statement.
*
* @param op The statement to select the entities for. The statement must be of boolean type.
* @param block Lambda that contains the entity updates
*
* @return All the updated entities that conform to the [op] statement.
*/
fun findManyByAndUpdate(op: Op<Boolean>, block: (it: T) -> Unit): SizedIterable<T> = find(op).forUpdate().mapLazy { it.apply { block(this) } }?.also { it.mapLazy { entity -> invalidateEntityInCache(entity) } }

/**
* Get an entity by its [id].
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,98 @@ class EntityTests : DatabaseTestsBase() {
}
}

@Test
fun testDaoFindByIdAndUpdate() {
withTables(Items) {
val oldPrice = 20.0
val item = Item.new {
name = "Item A"
price = oldPrice
}
assertEquals(oldPrice, item.price)
assertNotNull(Item.testCache(item.id))

val newPrice = 50.0
val updatedItem = Item.findByIdAndUpdate(item.id.value) {
it.price = newPrice
}
assertNotNull(updatedItem)
assertEquals(newPrice, updatedItem.price)
assertNull(Item.testCache(item.id))

assertEquals(oldPrice, item.price)
item.refresh(flush = false)
assertEquals(newPrice, item.price)
assertNotNull(Item.testCache(item.id))
}
}

@Test
fun testDaoFindSingleByAndUpdate() {
withTables(Items) {
val oldPrice = 20.0
val item = Item.new {
name = "Item A"
price = oldPrice
}
assertEquals(oldPrice, item.price)
assertNotNull(Item.testCache(item.id))

val newPrice = 50.0
val updatedItem = Item.findSingleByAndUpdate(Items.name eq "Item A") {
it.price = newPrice
}
assertNotNull(updatedItem)
assertEquals(newPrice, updatedItem.price)
assertNull(Item.testCache(item.id))

assertEquals(oldPrice, item.price)
item.refresh(flush = false)
assertEquals(newPrice, item.price)
assertNotNull(Item.testCache(item.id))
}
}

@Test
fun testDaoFindManyByAndUpdate() {
withTables(Items) {
val itemA = Item.new {
name = "New item"
price = 20.0
}
assertEquals(20.0, itemA.price)
assertNotNull(Item.testCache(itemA.id))
val itemB = Item.new {
name = "New item"
price = 30.0
}
assertEquals(30.0, itemB.price)
assertNotNull(Item.testCache(itemB.id))

val newPrice = 50.0
val updatedItems = Item.findManyByAndUpdate(Items.name eq "New item") {
it.price = newPrice
}
assertEquals(2, updatedItems.count())
updatedItems.mapLazy {
assert(listOf(itemA.id.value, itemB.id.value).contains(it.id.value))
assertEquals(newPrice, it.price)
assertEquals("New item", it.name)
}

assertNull(Item.testCache(itemA.id))
assertNull(Item.testCache(itemB.id))
assertEquals(20.0, itemA.price)
assertEquals(30.0, itemB.price)
itemA.refresh(flush = false)
itemB.refresh(flush = false)
assertEquals(newPrice, itemA.price)
assertEquals(newPrice, itemB.price)
assertNotNull(Item.testCache(itemA.id))
assertNotNull(Item.testCache(itemB.id))
}
}

object Humans : IntIdTable("human") {
val h = text("h", eagerLoading = true)
}
Expand Down

0 comments on commit af3f478

Please sign in to comment.