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

标记BaseTable的asExpression为open #523

Closed
wants to merge 2 commits into from
Closed

标记BaseTable的asExpression为open #523

wants to merge 2 commits into from

Conversation

qumn
Copy link
Contributor

@qumn qumn commented Oct 17, 2023

将 BaseTable 的 asExpression 为 open, 如此 BaseTable 可以通过重写 asExpression 方法在 extraProperties 放入一些信息.
在自定义实现的 Dialect 中实现一些操作, 例如: 逻辑删除, 更新/新增时的自动填充
详细可以参考这个仓库 逻辑删除, 自动填充

一个逻辑删除的案例:
定义一个 BaseTable 的基类集成自 Table

const val LOGICAL_DELETED_COLUMN: String = "LOGICAL_DELETED_COLUMN"

abstract class BaseTable<E : BaseEntity<E>>() : Table<E>() {
    private val _extraProperties = mutableMapOf<String, Any>()

    val deleted = boolean("deleted").logicalDeleted().bindTo { it.deleted }

    public fun Column<Boolean>.logicalDeleted(): Column<Boolean> {
        // save the information of logical deleted column into extraProperties
        // for using in logicalVisitorInterceptor
        _extraProperties[LOGICAL_DELETED_COLUMN] = this.name
        return this
    }

    override fun asExpression(): TableExpression {
        return TableExpression(tableName, alias, catalog, schema, extraProperties = _extraProperties.toMap())
    }
}
// template disable the logical delete for sequence api
fun <E : Any, T : BaseTable<E>> EntitySequence<E, T>.disableLogicalDeleted(): EntitySequence<E, T> {
    return this.withExpression(
        expression = this.expression.copy(
            from = disableLogicalDeletedHelper(
                this.expression.from,
                this.sourceTable.tableName
            )
        )
    )
}
// template disable the logical delete for dsl api
fun Query.disableLogicalDeleted(): Query {
    return this.withExpression(
        when (expression) {
            is SelectExpression -> {
                val selectExpression = expression as SelectExpression
                selectExpression.copy(from = disableLogicalDeletedHelper(selectExpression.from, null))
            }

            is UnionExpression -> throw IllegalStateException("logical delete is not supported in a union expression.")
        }
    )
}

/**
 * recursive the LOGICAL_DELETED_COLUMN key in extraProperties of tableExpression
 * @param tableName if the table name is not null remove properties of the tableExpression naming the `tableName`
 *                  otherwise remove the properties of all tableExpression
 */
private fun disableLogicalDeletedHelper(expression: QuerySourceExpression, tableName: String?): QuerySourceExpression {
    return when (expression) {
        is TableExpression -> {
            if (tableName == null || tableName == expression.name) {
                expression.copy(
                    extraProperties =
                    expression.extraProperties.filter { it.key != LOGICAL_DELETED_COLUMN }
                )
            } else {
                expression
            }
        }

        is JoinExpression -> {
            expression.copy(
                left = disableLogicalDeletedHelper(expression.left, tableName),
                right = disableLogicalDeletedHelper(expression.right, tableName)
            )
        }

        else -> expression
    }
}

实现一个 LogicalVisitorInterceptor 为 依据 extraProperties 中保存的信息构建查询条件

class LogicalVisitorInterceptor : SqlExpressionVisitorInterceptor {

    // will execute only once, because the function always return non-null
    override fun intercept(expr: SqlExpression, visitor: SqlExpressionVisitor): SqlExpression {
        return interceptHelper(expr).first
    }

    @Suppress("UNCHECKED_CAST")
    private fun <T : SqlExpression> interceptHelper(
        expr: T,
    ): Pair<T, ScalarExpression<Boolean>?> {
        return when (expr) {
            is SelectExpression -> {
                val (from, condition) = interceptHelper(expr.from)
                Pair(
                    expr.copy(
                        from = from,
                        where = expr.where.and(condition)
                    ) as T,
                    null
                )
            }

            is TableExpression -> {
                val conditions = buildLogicalDeleteExpression(expr)
                Pair(expr, conditions)
            }

            is JoinExpression -> {
                val (left, leftCondition) = interceptHelper(expr.left)
                val (right, rightCondition) = interceptHelper(expr.right)

                // if the `expr` parent is joinExpression, the superCondition will be used as join condition or return to grandfather based on the join type
                // if the `expr` parent is selectExpression, the superCondition will be used as where condition
                val (joinCondition, superCondition) = when (expr.type) {
                    JoinType.LEFT_JOIN -> Pair(rightCondition, leftCondition)
                    JoinType.RIGHT_JOIN -> Pair(leftCondition, rightCondition)
                    JoinType.INNER_JOIN -> Pair(leftCondition.and(rightCondition), null)
                    JoinType.FULL_JOIN -> Pair(null, null) // not support full join logical delete
                    JoinType.CROSS_JOIN -> Pair(null, leftCondition.and(rightCondition))
                }

                Pair(
                    expr.copy(left = left, right = right, condition = expr.condition.and(joinCondition)) as T,
                    superCondition
                )
            }

            else -> Pair(expr, null)
        }

    }

    private fun buildLogicalDeleteExpression(
        expr: TableExpression,
    ): BinaryExpression<Boolean>? {
        val logicalColumnName = expr.extraProperties[LOGICAL_DELETED_COLUMN] as String? ?: return null
        return BinaryExpression(
            type = BinaryExpressionType.EQUAL,
            left = ColumnExpression(table = expr, name = logicalColumnName, sqlType = BooleanSqlType),
            right = ArgumentExpression(value = false, BooleanSqlType),
            sqlType = BooleanSqlType
        )
    }

    private fun ScalarExpression<Boolean>?.and(condition: ScalarExpression<Boolean>?): ScalarExpression<Boolean>? {
        if (this == null) {
            return condition
        }
        if (condition == null) {
            return this
        }
        return this and condition
    }

}

省略一些代码, 详情的看这个 仓库

最后效果

定义如下 Entity

    interface Employee : BaseEntity<Employee> {
        companion object : Entity.Factory<Employee>()
        ...
    }

    interface Position : BaseEntity<Position> {
        companion object : Entity.Factory<Position>()
        ...
    }

    interface Customer : BaseEntity<Customer> {
        companion object : Entity.Factory<Customer>()
        ...
    }

    open class Departments(alias: String?) : BaseTable<Department>("t_department", alias) {
        companion object : Departments(null)

        ...
    }

    open class Employees(alias: String?) : BaseTable<Employee>("t_employee", alias) {
        companion object : Employees(null)
        ...
    }

    open class Positions(alias: String?) : BaseTable<Position>("t_position", alias) {
        companion object : Positions(null)
        ...
    }

DSL api

    @Test
    fun dslSQl() {
        val sql = database.from(Departments)
            .leftJoin(Employees, on = Employees.departmentId eq Departments.id)
            .leftJoin(Positions, on = Positions.id eq Employees.positionId)
            .select()
            .sql
        println(sql)
    }
SELECT * 
FROM "t_department" 
LEFT JOIN "t_employee" ON ("t_employee"."department_id" = "t_department"."id") AND ("t_employee"."deleted" = ?) 
LEFT JOIN "t_position" ON ("t_position"."id" = "t_employee"."position_id") AND ("t_position"."deleted" = ?) 
WHERE "t_department"."deleted" = ? 

sequence Api

    @Test
    fun sequenceAPI() {
        println(database.employees.sql)
    }
SELECT "t_employee"."uid" AS "t_employee_uid", "t_employee"."dept_id" AS "t_employee_dept_id", "t_employee"."deleted" AS "t_employee_deleted", "t_employee"."created_at" AS "t_employee_created_at", "t_employee"."updated_at" AS "t_employee_updated_at", "t_employee"."id" AS "t_employee_id", "t_employee"."name" AS "t_employee_name", "t_employee"."job" AS "t_employee_job", "t_employee"."manager_id" AS "t_employee_manager_id", "t_employee"."hire_date" AS "t_employee_hire_date", "t_employee"."salary" AS "t_employee_salary", "t_employee"."department_id" AS "t_employee_department_id", "t_employee"."position_id" AS "t_employee_position_id", "_ref0"."uid" AS "_ref0_uid", "_ref0"."dept_id" AS "_ref0_dept_id", "_ref0"."deleted" AS "_ref0_deleted", "_ref0"."created_at" AS "_ref0_created_at", "_ref0"."updated_at" AS "_ref0_updated_at", "_ref0"."id" AS "_ref0_id", "_ref0"."name" AS "_ref0_name", "_ref0"."location" AS "_ref0_location", "_ref0"."mixedCase" AS "_ref0_mixedCase", "_ref1"."uid" AS "_ref1_uid", "_ref1"."dept_id" AS "_ref1_dept_id", "_ref1"."deleted" AS "_ref1_deleted", "_ref1"."created_at" AS "_ref1_created_at", "_ref1"."updated_at" AS "_ref1_updated_at", "_ref1"."id" AS "_ref1_id", "_ref1"."name" AS "_ref1_name" 
FROM "t_employee" 
LEFT JOIN "t_department" AS "_ref0" ON ("t_employee"."department_id" = "_ref0"."id") AND ("_ref0"."deleted" = ?) 
LEFT JOIN "t_position" AS "_ref1" ON ("t_employee"."position_id" = "_ref1"."id") AND ("_ref1"."deleted" = ?) 
WHERE "t_employee"."deleted" = ? 

@qumn qumn closed this Apr 18, 2024
@vincentlauvlwj
Copy link
Member

重新打开这个 PR,开放 asExpression 确实对外部扩展有价值

@qumn
Copy link
Contributor Author

qumn commented May 21, 2024

好的,我把后面自己本地使用修改jackson版本的commit删除了

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants