Newer
Older
bremer / src / main / kotlin / utils / SqlUtils.kt
/*
 * Copyright (c) 2023. yo-saito. All Rights Reserved.
 */

package net.piedpiper.bremer.utils

import net.piedpiper.bremer.utils.sql.Column
import net.piedpiper.bremer.utils.sql.Table
import org.apache.ibatis.jdbc.SQL
import java.time.LocalDateTime
import kotlin.reflect.KClass
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberProperties
import kotlin.reflect.javaType

class SelectSQLBuilder<T : Any>(tableClass: KClass<T>) {
    private val tableName: String =
        tableClass.findAnnotation<Table>()?.name ?: throw IllegalArgumentException()
    private val joinTableMap : MutableMap<String, String> = mutableMapOf()
    private val sql: SQL = SQL().FROM(tableName)
    private var columns: String =
        tableClass.memberProperties
            .mapNotNull { it.findAnnotation<Column>()?.name }
            .joinToString(separator = ",") { "${tableName}.${it} AS ${it}" }

    fun <U : Any> leftOuterJoin(
        tableClass: KClass<U>,
        cond: String,
    ): SelectSQLBuilder<T> {
        return leftOuterJoin(tableClass, cond, Unit::class);
    }

    fun <U : Any, V : Any> leftOuterJoin(
        tableClass: KClass<U>,
        cond: String,
        parentTableClass: KClass<V>
    ): SelectSQLBuilder<T> {
        val joinTableName = tableClass.findAnnotation<Table>()?.name
            ?: throw IllegalArgumentException()
        val col : String
        if (parentTableClass == Unit::class) {
            col = joinTableName
        } else {
            val joinParentTableName = parentTableClass.findAnnotation<Table>()?.name
                ?: throw IllegalArgumentException()
            if (joinTableMap.containsKey(joinParentTableName)) {
                col = "${joinTableMap[joinParentTableName]}__${joinTableName}"
                joinTableMap.put(joinTableName, col)
            } else {
                throw IllegalArgumentException()
            }
        }
        joinTableMap.put(joinTableName, col)
        columns += "," + tableClass.memberProperties.mapNotNull { it.findAnnotation<Column>()?.name }
            .joinToString(separator = ",") { "${joinTableName}.${it} AS ${col}__${it}" }
        sql.LEFT_OUTER_JOIN("${joinTableName} ON ${cond}")
        return this
    }

    fun toSql(): SQL {
        return sql.SELECT(columns)
    }
}

object DaoUtils {
    @OptIn(ExperimentalStdlibApi::class)
    @JvmStatic
    fun <T : Any> insertOne(entity: T): String {
        val properties = entity::class.memberProperties
            .filter {
                it.findAnnotation<Column>()?.let { it.insertable } ?: false
            }
            .filter { it.getter.call(entity) != null }
        val sql =
            SQL().INSERT_INTO(
                entity::class.findAnnotation<Table>()?.name
                    ?: throw java.lang.IllegalArgumentException()
            );
        val columns = properties.map { it.findAnnotation<Column>()?.name }
        val values = properties.map {
            if (it.returnType.javaType == LocalDateTime::class.java)
                "#{entity.${it?.name}, typeHandler=net.piedpiper.bremer.dao.handler.LocalDateTimeTypeHandler}"
            else
                "#{entity.${it?.name}}"
        }
        repeat(columns.size) {
            sql.VALUES(columns[it], values[it])
        }
        return sql.toString()
    }

    @OptIn(ExperimentalStdlibApi::class)
    @JvmStatic
    fun <T : Any> updateOne(entity: T): String {
        val properties = entity::class.memberProperties
            .filter {
                val column = it.findAnnotation<Column>()
                column != null && column.insertable
            }
            .filter { it.getter.call(entity) != null }
        val sql =
            SQL().UPDATE(
                entity::class.findAnnotation<Table>()?.name
                    ?: throw java.lang.IllegalArgumentException()
            );
        properties.forEach {
            val v = if (it.returnType.javaType == LocalDateTime::class.java)
                "#{entity.${it?.name}, typeHandler=net.piedpiper.bremer.dao.handler.LocalDateTimeTypeHandler}"
            else
                "#{entity.${it?.name}}"
            sql.SET("${it.findAnnotation<Column>()?.name} = ${v}")
        }
        return sql.WHERE("id = #{entity.id}").toString()
    }

    @JvmStatic
    fun <T : Any> deleteQuery(
        clazz: KClass<T>,
    ): SQL = SQL().DELETE_FROM(
        clazz.findAnnotation<Table>()?.name
            ?: throw java.lang.IllegalArgumentException()
    )

    @JvmStatic
    private fun <T : Any> deleteFrom(
        clazz: KClass<T>,
    ): SQL = SQL().DELETE_FROM(
        clazz.findAnnotation<Table>()?.name
            ?: throw java.lang.IllegalArgumentException()
    )
}

fun SQL.WHERE_IN(
    targetColumn: String,
    parameterName: String,
    num: Int,
): SQL {
    if (num == 0) {
        throw IllegalArgumentException()
    }
    generateSequence(0) { v -> v + 1 }.take(num).map {
        "#{${parameterName}[${it}]}"
    }.joinToString(",", "(", ")").let {
        WHERE("${targetColumn} IN ${it}")
    }
    return this
}

fun SQL.WHERE_LIKES(
    targetColumn: String,
    parameterName: String,
    num: Int,
): SQL {
    if (num == 0) {
        throw IllegalArgumentException()
    }
    generateSequence(0) { v -> v + 1 }.take(num).map {
        "${targetColumn} LIKE CONCAT('%', #{${parameterName}[${it}]}, '%')"
    }.joinToString(" AND ").let {
        WHERE("${it}")
    }
    return this
}