Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: PostgreSQL DSL #1456

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,41 +58,45 @@ abstract class AbstractQuery<T : AbstractQuery<T>>(targets: List<Table>) : Sized
protected abstract val queryToExecute: Statement<ResultSet>

override fun iterator(): Iterator<ResultRow> {
val resultIterator = ResultIterator(transaction.exec(queryToExecute)!!)
val resultIterator = ResultIterator(transaction.exec(queryToExecute)!!, transaction, set)
return if (transaction.db.supportsMultipleResultSets) {
resultIterator
} else {
Iterable { resultIterator }.toList().iterator()
}
}
}

private inner class ResultIterator(val rs: ResultSet) : Iterator<ResultRow> {
private var hasNext = false
set(value) {
field = value
if (!field) {
rs.statement?.close()
transaction.openResultSetsCount--
}
class ResultIterator(
private val rs: ResultSet,
private val transaction: Transaction,
set: FieldSet
) : Iterator<ResultRow> {
private var hasNext = false
set(value) {
field = value
if (!field) {
rs.statement?.close()
transaction.openResultSetsCount--
}

private val fieldsIndex = set.realFields.toSet().mapIndexed { index, expression -> expression to index }.toMap()

init {
hasNext = rs.next()
if (hasNext) trackResultSet(transaction)
}

override operator fun next(): ResultRow {
if (!hasNext) throw NoSuchElementException()
val result = ResultRow.create(rs, fieldsIndex)
hasNext = rs.next()
return result
}
private val fieldsIndex = set.realFields.toSet().mapIndexed { index, expression -> expression to index }.toMap()

init {
hasNext = rs.next()
if (hasNext) trackResultSet(transaction)
}

override fun hasNext(): Boolean = hasNext
override operator fun next(): ResultRow {
if (!hasNext) throw NoSuchElementException()
val result = ResultRow.create(rs, fieldsIndex)
hasNext = rs.next()
return result
}

override fun hasNext(): Boolean = hasNext

companion object {
private fun trackResultSet(transaction: Transaction) {
val threshold = transaction.db.config.logTooMuchResultSetsThreshold
Expand All @@ -104,5 +108,11 @@ abstract class AbstractQuery<T : AbstractQuery<T>>(targets: List<Table>) : Sized
}
transaction.openResultSetsCount++
}

val empty = object : Iterator<ResultRow> {
override fun hasNext(): Boolean = false

override fun next(): ResultRow = TODO("Can't return next when hasNext return false")
}
}
}
12 changes: 10 additions & 2 deletions exposed-core/src/main/kotlin/org/jetbrains/exposed/sql/Database.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,19 @@ import org.jetbrains.exposed.sql.statements.api.ExposedDatabaseMetadata
import org.jetbrains.exposed.sql.transactions.DEFAULT_ISOLATION_LEVEL
import org.jetbrains.exposed.sql.transactions.ThreadLocalTransactionManager
import org.jetbrains.exposed.sql.transactions.TransactionManager
import org.jetbrains.exposed.sql.vendors.*
import org.jetbrains.exposed.sql.vendors.DatabaseDialect
import org.jetbrains.exposed.sql.vendors.H2Dialect
import org.jetbrains.exposed.sql.vendors.MariaDBDialect
import org.jetbrains.exposed.sql.vendors.MysqlDialect
import org.jetbrains.exposed.sql.vendors.OracleDialect
import org.jetbrains.exposed.sql.vendors.PostgreSQLDialect
import org.jetbrains.exposed.sql.vendors.PostgreSQLNGDialect
import org.jetbrains.exposed.sql.vendors.SQLServerDialect
import org.jetbrains.exposed.sql.vendors.SQLiteDialect
import java.math.BigDecimal
import java.sql.Connection
import java.sql.DriverManager
import java.util.*
import java.util.ServiceLoader
import java.util.concurrent.ConcurrentHashMap
import javax.sql.ConnectionPoolDataSource
import javax.sql.DataSource
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.jetbrains.exposed.sql.render

import org.jetbrains.exposed.sql.QueryBuilder

/**
* Render callbacks for insert statement - order of invoked callbacks may differ per SQL dialect.
* Some may not be invoked at all in dialect.
*/
interface RenderInsertSQLCallbacks {
/**
* Render `RETURNING ...` statement
*
* Used in: Postgresql
*/
fun returning(builder: QueryBuilder) {}

/**
* Render `ON CONFLICT ...`
*
* Used in: Postgresql
*/
fun onConflict(builder: QueryBuilder) {}

object Noop : RenderInsertSQLCallbacks
}

interface RenderUpdateSQLCallbacks {
/**
* Render `RETURNING ...`
*
* Used in: Postgresql
*/
fun returning(builder: QueryBuilder) {}

object Noop : RenderUpdateSQLCallbacks
}

interface RenderDeleteSQLCallbacks {
/**
* Render `RETURNING ...` statement
*
* Used in: Postgresql
*/
fun returning(builder: QueryBuilder) {}

object Noop : RenderDeleteSQLCallbacks
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,40 @@ import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.render.RenderDeleteSQLCallbacks
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi

open class DeleteStatement(
abstract class AbstractDeleteStatement<STATEMENT_RETURN>(
val table: Table,
val where: Op<Boolean>? = null,
var where: Op<Boolean>? = null,
val isIgnore: Boolean = false,
val limit: Int? = null,
val offset: Long? = null
) : Statement<Int>(StatementType.DELETE, listOf(table)) {
val offset: Long? = null,
var renderSqlCallback: RenderDeleteSQLCallbacks = RenderDeleteSQLCallbacks.Noop
) : Statement<STATEMENT_RETURN>(StatementType.DELETE, listOf(table)) {

override fun PreparedStatementApi.executeInternal(transaction: Transaction): Int {
return executeUpdate()
override fun prepareSQL(transaction: Transaction): String {
val where = where?.let { QueryBuilder(true).append(it).toString() }
return transaction.db.dialect.functionProvider.delete(isIgnore, table, where, limit, transaction, renderSqlCallback)
}

override fun prepareSQL(transaction: Transaction): String =
transaction.db.dialect.functionProvider.delete(isIgnore, table, where?.let { QueryBuilder(true).append(it).toString() }, limit, transaction)

override fun arguments(): Iterable<Iterable<Pair<IColumnType, Any?>>> = QueryBuilder(true).run {
where?.toQueryBuilder(this)
listOf(args)
}
}

open class DeleteStatement(
table: Table,
where: Op<Boolean>? = null,
isIgnore: Boolean = false,
limit: Int? = null,
offset: Long? = null
) : AbstractDeleteStatement<Int>(table, where, isIgnore, limit, offset) {

override fun PreparedStatementApi.executeInternal(transaction: Transaction): Int {
return executeUpdate()
}

companion object {
fun where(transaction: Transaction, table: Table, op: Op<Boolean>, isIgnore: Boolean = false, limit: Int? = null, offset: Long? = null): Int =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package org.jetbrains.exposed.sql.statements

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.autoIncColumnType
import org.jetbrains.exposed.sql.render.RenderInsertSQLCallbacks
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi
import java.sql.ResultSet

Expand Down Expand Up @@ -42,7 +46,7 @@ open class SQLServerBatchInsertStatement(table: Table, ignore: Boolean = false,
}
}.toString()
}
return transaction.db.dialect.functionProvider.insert(isIgnore, table, values.firstOrNull()?.map { it.first }.orEmpty(), sql, transaction)
return transaction.db.dialect.functionProvider.insert(isIgnore, table, values.firstOrNull()?.map { it.first }.orEmpty(), sql, transaction, RenderInsertSQLCallbacks.Noop)
}

override fun arguments() = listOfNotNull(super.arguments().flatten().takeIf { data.isNotEmpty() })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import org.jetbrains.exposed.sql.AbstractQuery
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.IColumnType
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.render.RenderInsertSQLCallbacks
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi

open class InsertSelectStatement(val columns: List<Column<*>>, val selectQuery: AbstractQuery<*>, val isIgnore: Boolean = false) :
Statement<Int>(StatementType.INSERT, listOf(columns.first().table)) {
open class InsertSelectStatement(
val columns: List<Column<*>>,
val selectQuery: AbstractQuery<*>,
val isIgnore: Boolean = false
) : Statement<Int>(StatementType.INSERT, listOf(columns.first().table)) {

init {
if (columns.isEmpty()) error("Can't insert without provided columns")
Expand All @@ -20,6 +24,15 @@ open class InsertSelectStatement(val columns: List<Column<*>>, val selectQuery:

override fun arguments(): Iterable<Iterable<Pair<IColumnType, Any?>>> = selectQuery.arguments()

override fun prepareSQL(transaction: Transaction): String =
transaction.db.dialect.functionProvider.insert(isIgnore, targets.single(), columns, selectQuery.prepareSQL(transaction), transaction)
override fun prepareSQL(transaction: Transaction): String {
return transaction.db.dialect.functionProvider.insert(
isIgnore,
targets.single(),
columns,
selectQuery.prepareSQL(transaction),
transaction,
RenderInsertSQLCallbacks.Noop
)
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
package org.jetbrains.exposed.sql.statements

import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.CompositeColumn
import org.jetbrains.exposed.sql.EntityIDColumnType
import org.jetbrains.exposed.sql.Expression
import org.jetbrains.exposed.sql.IColumnType
import org.jetbrains.exposed.sql.NextVal
import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.ResultRow
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.autoIncColumnType
import org.jetbrains.exposed.sql.isAutoInc
import org.jetbrains.exposed.sql.render.RenderInsertSQLCallbacks
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi
import org.jetbrains.exposed.sql.vendors.PostgreSQLDialect
import org.jetbrains.exposed.sql.vendors.currentDialect
Expand All @@ -16,6 +28,8 @@ open class InsertStatement<Key : Any>(val table: Table, val isIgnore: Boolean =
var resultedValues: List<ResultRow>? = null
private set

var renderSqlCallback: RenderInsertSQLCallbacks = RenderInsertSQLCallbacks.Noop

infix operator fun <T> get(column: Column<T>): T {
val row = resultedValues?.firstOrNull() ?: error("No key generated")
return row[column]
Expand Down Expand Up @@ -109,9 +123,10 @@ open class InsertStatement<Key : Any>(val table: Table, val isIgnore: Boolean =
values.appendTo(prefix = "VALUES (", postfix = ")") { (col, value) ->
registerArgument(col, value)
}

toString()
}
return transaction.db.dialect.functionProvider.insert(isIgnore, table, values.map { it.first }, sql, transaction)
return transaction.db.dialect.functionProvider.insert(isIgnore, table, values.map { it.first }, sql, transaction, renderSqlCallback)
}

protected open fun PreparedStatementApi.execInsertFunction(): Pair<Int, ResultSet?> {
Expand Down Expand Up @@ -177,4 +192,4 @@ open class InsertStatement<Key : Any>(val table: Table, val isIgnore: Boolean =
builder.args
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
package org.jetbrains.exposed.sql.statements

import org.jetbrains.exposed.exceptions.throwUnsupportedException
import org.jetbrains.exposed.sql.*
import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.ColumnSet
import org.jetbrains.exposed.sql.IColumnType
import org.jetbrains.exposed.sql.Join
import org.jetbrains.exposed.sql.Op
import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.Table
import org.jetbrains.exposed.sql.Transaction
import org.jetbrains.exposed.sql.render.RenderUpdateSQLCallbacks
import org.jetbrains.exposed.sql.statements.api.PreparedStatementApi
import org.jetbrains.exposed.sql.targetTables

open class UpdateStatement(val targetsSet: ColumnSet, val limit: Int?, val where: Op<Boolean>? = null) :
UpdateBuilder<Int>(StatementType.UPDATE, targetsSet.targetTables()) {
abstract class AbstractUpdateStatement<R>(
var targetsSet: ColumnSet,
val limit: Int?,
var where: Op<Boolean>?
) : UpdateBuilder<R>(StatementType.UPDATE, targetsSet.targetTables()) {

open val firstDataSet: List<Pair<Column<*>, Any?>> get() = values.toList()

override fun PreparedStatementApi.executeInternal(transaction: Transaction): Int {
if (values.isEmpty()) return 0
return executeUpdate()
}
var sqlRendererCallback: RenderUpdateSQLCallbacks = RenderUpdateSQLCallbacks.Noop

override fun prepareSQL(transaction: Transaction): String {
require(firstDataSet.isNotEmpty()) { "Can't prepare UPDATE statement without fields to update" }

return when (targetsSet) {
is Table -> transaction.db.dialect.functionProvider.update(targetsSet, firstDataSet, limit, where, transaction)
is Join -> transaction.db.dialect.functionProvider.update(targetsSet, firstDataSet, limit, where, transaction)
else -> transaction.throwUnsupportedException("UPDATE with ${targetsSet::class.simpleName} unsupported")
return when (val selectedTargetSet = targetsSet) {
is Table -> transaction.db.dialect.functionProvider.update(selectedTargetSet, firstDataSet, limit, where, transaction, sqlRendererCallback)
is Join -> transaction.db.dialect.functionProvider.update(selectedTargetSet, firstDataSet, limit, where, transaction, sqlRendererCallback)
else -> transaction.throwUnsupportedException("UPDATE with ${selectedTargetSet::class.simpleName} unsupported")
}
}

Expand All @@ -32,3 +40,15 @@ open class UpdateStatement(val targetsSet: ColumnSet, val limit: Int?, val where
if (args.isNotEmpty()) listOf(args) else emptyList()
}
}

open class UpdateStatement(
targetsSet: ColumnSet,
limit: Int? = null,
where: Op<Boolean>? = null
) : AbstractUpdateStatement<Int>(targetsSet, limit, where) {

override fun PreparedStatementApi.executeInternal(transaction: Transaction): Int {
if (values.isEmpty()) return 0
return executeUpdate()
}
}
Loading