I currently use the below approach to reuse select statement. This approach works really well, but this approach doesn't provide me type safety as it is json function. Therefore, I would like to introduce the same pattern to select query with multiset
for nested collection and row
for nested single object without jsonObject.
data class FoodRecord(
val id: UUID,
val createdAt: Instant,
val createdBy: UserGeneralRecord,
val lastModifiedAt: Instant,
val lastModifiedBy: UserGeneralRecord,
val name: String
val synonyms: List<String>
)
data class UserGeneralRecord(
val id: UUID,
val nickname: String,
val role: UserRole
) {
companion object {
private val user = JUser.USER
val select = jsonObject(
key("id").value(user.ID),
key("nickname").value(user.NICKNAME),
key("role").value(user.ROLE)
)
}
}
private val food: JFood = JFood.FOOD
private val foodSynonym: JFoodSynonym = JFoodSynonym.FOOD_SYNONYM
private val user: JUser = JUser.USER
override fun fetch(query: FetchFoodByIdQuery): FoodRecord {
return ctx.getContext()
.select(
jsonObject(
key("id").value(food.ID),
key("createdAt").value(food.CREATED_AT),
key("createdBy").value(
field(
select(UserGeneralRecord.select)
.from(user)
.where(user.ID.eq(food.CREATED_BY))
)
),
key("lastModifiedAt").value(food.LAST_MODIFIED_AT),
key("lastModifiedBy").value(
field(
select(UserGeneralRecord.select)
.from(user)
.where(user.ID.eq(food.LAST_MODIFIED_BY))
)
),
key("name").value(food.NAME),
key("synonyms").value(
field(
select(jsonArrayAgg(foodSynonym.SYNONYM))
.from(foodSynonym)
.where(foodSynonym.FOOD_ID.eq(food.ID))
)
),
)
)
.from(food)
.where(food.ID.eq(query.id))
.fetchOneInto(FoodRecord::class.java)
?: throw NoResultFoundException("Food is not found by id: ${query.id}")
}
I'm looking for a possible approach without using jsonObject as below(the below is not working, but only for showing my idea.)
override fun fetch(query: FetchFoodByIdQuery): FoodRecord {
return ctx.getContext()
.select(
food.ID,
food.CREATED_AT,
row(
select( // This select should be reused (same as lastModifiedBy)
user.ID,
user.NICKNAME,
user.ROLE
).from(user)
.where(user.ID.eq(food.CREATED_BY))
).`as`("createdBy"),
food.LAST_MODIFIED_AT,
row(
select( // This select should be reused (same as createdBy)
user.ID,
user.NICKNAME,
user.ROLE
).from(user)
.where(user.ID.eq(food.LAST_MODIFIED_BY))
).`as`("lastModifiedBy"),
multiset(
select(foodSynonym.SYNONYM).from(foodSynonym)
.where(foodSynonym.FOOD_ID.eq(food.ID))
).`as`("synonyms")
)
.from(food)
.where(food.ID.eq(query.id))
.fetchOneInto(FoodRecord::class.java)
?: throw NoResultFoundException("Food is not found by id: ${query.id}")
}
This may just be a matter of understanding how to compose different elements to achieve what you want. Probably, this would work for you?
field(
select(
row(
user.ID,
user.NICKNAME,
user.ROLE
)
).from(user)
.where(user.ID.eq(food.CREATED_BY))
).`as`("createdBy")
Incidentally, this is also how you'd do this with native PostgreSQL. There's no ROW(SELECT ..)
constructor in PostgreSQL or standard SQL, like there's a MULTISET(SELECT ..)
constructor.
If you want to reuse the row()
expression, just assign it to a local variable or method, like you did in the jsonObject()
case.