diff --git a/rest-api/src/main/kotlin/com/example/api/tweeter/ApiController.kt b/rest-api/src/main/kotlin/com/example/api/tweeter/ApiController.kt index 31932ab..9531049 100644 --- a/rest-api/src/main/kotlin/com/example/api/tweeter/ApiController.kt +++ b/rest-api/src/main/kotlin/com/example/api/tweeter/ApiController.kt @@ -10,6 +10,8 @@ import com.example.api.tweeter.search.TweeterSearchHandler import com.example.api.tweeter.search.TweeterSearchRequest import com.example.api.tweeter.search.TweeterSearchResponse import com.example.config.Jackson +import com.example.util.exposed.functions.postgres.distinctOn +import com.example.util.exposed.query.toSQL import com.fasterxml.jackson.databind.JsonNode import io.burt.jmespath.Expression import io.burt.jmespath.JmesPath @@ -19,11 +21,13 @@ import io.swagger.annotations.ApiResponse import io.swagger.annotations.ApiResponses import mu.KLogging import org.funktionale.option.Option +import org.jetbrains.exposed.sql.* +import org.jetbrains.exposed.sql.SqlExpressionBuilder.greaterEq +import org.springframework.transaction.annotation.Transactional import org.springframework.web.bind.annotation.* import java.time.Instant import java.util.* - @RestController class TweeterApiController( private val repo: TweetsRepo, @@ -37,6 +41,33 @@ class TweeterApiController( fun getOne(@PathVariable id: UUID): TweetDto = repo[id].toTweetsDto() + @GetMapping("/api/tweeter/distinctOn") + @Transactional(readOnly = true) + fun getDistinct(): List { + val selectDistinctOn: CustomFunction = distinctOn( + TweetsTable.message, + TweetsTable.comment + ) + val selectColumns: List> = (TweetsTable.columns) + val selectWhere: Op = ( + TweetsTable.createdAt.greaterEq(Instant.EPOCH) + ) + + val query: Query = TweetsTable + .slice( + selectDistinctOn, + *selectColumns.toTypedArray() + ) + .select { selectWhere } + + val SQL: String = query.toSQL() + logger.info { "==== Select Distinct On Example === SQL: $SQL " } + val dtos: List = query + .map { with(TweetsTable) { it.toTweetsRecord() } } + .map { it.toTweetsDto() } + return dtos + } + @PutMapping("/api/tweeter") fun createOne(@RequestBody req: CreateTweetRequest): TweetDto = req.toRecord(id = UUID.randomUUID(), now = Instant.now()) diff --git a/rest-api/src/main/kotlin/com/example/util/exposed/expr/postgres/DistinctOn.kt b/rest-api/src/main/kotlin/com/example/util/exposed/expr/postgres/DistinctOn.kt deleted file mode 100644 index 98de4cf..0000000 --- a/rest-api/src/main/kotlin/com/example/util/exposed/expr/postgres/DistinctOn.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.example.util.exposed.expr.postgres - -import org.jetbrains.exposed.sql.Column -import org.jetbrains.exposed.sql.Expression -import org.jetbrains.exposed.sql.IntegerColumnType -import org.jetbrains.exposed.sql.QueryBuilder - -// see: https://github.com/JetBrains/Exposed/issues/500 - -/* -fun Column<*>.distinctOn(): Function = DistinctOn(this) - -class DistinctOn(val expr: Expression<*>) : Function(IntegerColumnType()) { - override fun toSQL(queryBuilder: QueryBuilder) = "DISTINCT ON (${expr.toSQL(queryBuilder)}) ${expr.toSQL(queryBuilder)}" -} - - */ diff --git a/rest-api/src/main/kotlin/com/example/util/exposed/functions/common/CustomBooleanFunction.kt b/rest-api/src/main/kotlin/com/example/util/exposed/functions/common/CustomBooleanFunction.kt new file mode 100644 index 0000000..57eeb15 --- /dev/null +++ b/rest-api/src/main/kotlin/com/example/util/exposed/functions/common/CustomBooleanFunction.kt @@ -0,0 +1,21 @@ +package com.example.util.exposed.functions.common + +import org.jetbrains.exposed.sql.BooleanColumnType +import org.jetbrains.exposed.sql.CustomFunction +import org.jetbrains.exposed.sql.Expression +import org.jetbrains.exposed.sql.QueryBuilder + +/** + * Boolean Function (with optional postfix) + */ +fun CustomBooleanFunction( + functionName: String, postfix: String = "", vararg params: Expression<*> +): CustomFunction = + object : CustomFunction(functionName, BooleanColumnType(), *params) { + override fun toQueryBuilder(queryBuilder: QueryBuilder) { + super.toQueryBuilder(queryBuilder) + if (postfix.isNotEmpty()) { + queryBuilder.append(postfix) + } + } + } diff --git a/rest-api/src/main/kotlin/com/example/util/exposed/functions/postgres/DistinctOn.kt b/rest-api/src/main/kotlin/com/example/util/exposed/functions/postgres/DistinctOn.kt new file mode 100644 index 0000000..6ba5186 --- /dev/null +++ b/rest-api/src/main/kotlin/com/example/util/exposed/functions/postgres/DistinctOn.kt @@ -0,0 +1,15 @@ +package com.example.util.exposed.functions.postgres + +import com.example.util.exposed.functions.common.CustomBooleanFunction +import org.jetbrains.exposed.sql.CustomFunction +import org.jetbrains.exposed.sql.Expression + +/** + * SELECT DISTINCT ON (a,b,c) TRUE, a,b,x,y,z FROM table WHERE ... + * see: https://github.com/JetBrains/Exposed/issues/500 + */ +fun distinctOn(vararg expressions: Expression<*>): CustomFunction = CustomBooleanFunction( + functionName = "DISTINCT ON", + postfix = " TRUE", + params = *expressions +) diff --git a/rest-api/src/main/kotlin/com/example/util/exposed/query/QueryExt.kt b/rest-api/src/main/kotlin/com/example/util/exposed/query/QueryExt.kt new file mode 100644 index 0000000..3dffe88 --- /dev/null +++ b/rest-api/src/main/kotlin/com/example/util/exposed/query/QueryExt.kt @@ -0,0 +1,6 @@ +package com.example.util.exposed.query + +import org.jetbrains.exposed.sql.Query +import org.jetbrains.exposed.sql.QueryBuilder + +fun Query.toSQL():String = prepareSQL(QueryBuilder(false))