Ktorm KSP extension to help generate boilerplate code.

Overview

Ktorm

🇺🇸 English | 🇨🇳 简体中文

What's Ktorm-ksp?

Ktorm KSP extension to help generate boilerplate code. It can automatically generate Table objects through entity classes, while making entities defined by data classes easier to use, and supports custom extension code generation logic.

  • PS: The project is still in development

Feature

  • Just write the entity class and automatically generate the corresponding Table object. Support classes defined based on the Entity interface, as well as entities defined by ordinary class or data class

  • Better support for class entity classes, the default implementation of the doCreateEntity method, and the add and update method of the entity sequence

  • Extensible code generation logic. Through the SPI mechanism, you only need to implement the specified interface, and you can write your own automatically generated logic.

custom entity ▼

@Table
public data class Student(
    @PrimaryKey
    public var id: Int?,
    public var name: String,
    public var age: Int
)

auto generate code ▼

= int("id").primaryKey() public val name: Column = varchar("name") public val age: Column = int("age") public override fun doCreateEntity(row: QueryRowSet, withReferences: Boolean): Student { return Student( id = row[id], name = row[name]!!, age = row[age]!!, ) } } public fun EntitySequence .add(entity: Student): Int { /* omit code */ } public fun EntitySequence .update(entity: Student): Int { /* omit code */ } public val Database.students: EntitySequence get() = this.sequenceOf(Students)">
public object Students : BaseTable<Student>(tableName="Student",entityClass=Student::class,) {
  public val id: Column<Int> = int("id").primaryKey()

  public val name: Column<String> = varchar("name")

  public val age: Column<Int> = int("age")

  public override fun doCreateEntity(row: QueryRowSet, withReferences: Boolean): Student {
    return Student(
      id = row[id],
      name = row[name]!!,
      age = row[age]!!,
    )
  }
}

public fun EntitySequence<Student, Students>.add(entity: Student): Int { /* omit code */ }

public fun EntitySequence<Student, Students>.update(entity: Student): Int { /* omit code */ }

public val Database.students: EntitySequence<Student, Students> get() = this.sequenceOf(Students)

Start

...

Table Definition

...

interface entity

...

class/data class entity

...

NamingStyle

...

TypeConverter

...

property or function generator

...

default property or function generator

...

custom property or function generator

...

Comments
  • 针对 interface 的实体对象,建议生成伪构造函数、componenntN、copy,支持 data class 一样的使用体验

    针对 interface 的实体对象,建议生成伪构造函数、componenntN、copy,支持 data class 一样的使用体验

    比如这样的实体类

    @Table
    interface Student : Entity<Student> {
        var id: Int
        var name: String
        var age: Int
    }
    

    生成代码:

    fun Student(id: Int = 0, name: String = "", age: Int = 0): Student {
        val entity = Entity.create<Student>()
        entity.id = id
        entity.name = name
        entity.age = age
        return entity
    }
    

    这样使用起来就像真正的 data class 一样:

    val s = Student(id = 1, name = "vince")
    database.students.add(s)
    
    enhancement 
    opened by vincentlauvlwj 37
  • 请问ksp的log具体是在哪看的?

    请问ksp的log具体是在哪看的?

    build目录下的log目录都是空的。。。 报错是:

    [ksp] java.util.NoSuchElementException: Sequence is empty.
    	at kotlin.sequences.SequencesKt___SequencesKt.first(_Sequences.kt:112)
    	at org.ktorm.ksp.compiler.KtormProcessor$EntityVisitor.visitClassDeclaration(KtormProcessorProvider.kt:217)
    	at org.ktorm.ksp.compiler.KtormProcessor$EntityVisitor.visitClassDeclaration(KtormProcessorProvider.kt:196)
    	at com.google.devtools.ksp.symbol.impl.kotlin.KSClassDeclarationImpl.accept(KSClassDeclarationImpl.kt:136)
    	at org.ktorm.ksp.compiler.KtormProcessor.process(KtormProcessorProvider.kt:90)
    	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$4$1.invoke(KotlinSymbolProcessingExtension.kt:237)
    	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension$doAnalysis$4$1.invoke(KotlinSymbolProcessingExtension.kt:235)
    	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.handleException(KotlinSymbolProcessingExtension.kt:330)
    	at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:235)
    	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:123)
    	at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:99)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:264)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler$analyze$1.invoke(KotlinToJVMBytecodeCompiler.kt:55)
    	at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:115)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:255)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:101)
    	at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli$default(KotlinToJVMBytecodeCompiler.kt:60)
    	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:157)
    	at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:52)
    	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:94)
    	at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:43)
    	at org.jetbrains.kotlin.cli.common.CLITool.exec(CLITool.kt:101)
    	at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1642)
    	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:577)
    	at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:360)
    	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:200)
    	at java.rmi/sun.rmi.transport.Transport$1.run(Transport.java:197)
    	at java.base/java.security.AccessController.doPrivileged(AccessController.java:712)
    	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
    	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:598)
    	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:844)
    	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:721)
    	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:720)
    	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    	at java.base/java.lang.Thread.run(Thread.java:833)
    
    
    opened by Anivie 10
  • 生成代码中使用的局部变量、表类型名称引用  可能会跟参数名、表类型扩展成员名称冲突

    生成代码中使用的局部变量、表类型名称引用 可能会跟参数名、表类型扩展成员名称冲突

    实体类型

    @Table(tableClassName = "entity")
    public data class Entity(
        @PrimaryKey
        public var id: Int?
    )
    

    生成表类型代码

    public object entity : BaseTable<Entity>(tableName = "entity", entityClass = Entity::class) {
        public val id: Column<Int> = int("id").primaryKey()
    
        public override fun doCreateEntity(row: QueryRowSet, withReferences: Boolean): Entity {
             return Entity(
                id = row[this.id],
            )
        }
    }
    

    这个entity表名可能会跟多处生成代码发生冲突

    • 生成的序列扩展名称和类型名冲突 image 考虑把sequenceOf的参数改成全限定类名,可以解决此问题(除非这个表类型的包名是空的)
    • 和生成的addupdate方法参数冲突 image 参数名和表类型名称都是entity导致报错,在其他生成方法也会出现这种情况,需要考虑将其参数改成不容易发生冲突的名称
    bug 
    opened by lookup-cat 9
  • 当使用class作为实体,并且构造参数只有非表字段(加了@Ignore)并且为默认参数时, 生成错误的doCreateEntity代码

    当使用class作为实体,并且构造参数只有非表字段(加了@Ignore)并且为默认参数时, 生成错误的doCreateEntity代码

    实体定义

    @Table
    public class Student(
        @Ignore
        public var createTime: LocalDateTime = LocalDateTime.now()
    ) {
        @PrimaryKey
        public var id: Int? = null
        public var name: String = ""
        public var age: Int = 0
    }
    

    实际生成doCreateEntity方法代码

    public override fun doCreateEntity(row: QueryRowSet, withReferences: Boolean): Student {
        val constructor = Student::class.primaryConstructor!!
        val parameterMap = HashMap<KParameter, Any?>(1)
        for (parameter in constructor.parameters) {
            when (parameter.name) {
            }
        }
        val entity = constructor.callBy(parameterMap)
        entity.id = row[this.id]
        entity.name = row[this.name]!!
        entity.age = row[this.age]!!
        return entity
    }
    

    合理的生成代码应该是

    public override fun doCreateEntity(row: QueryRowSet, withReferences: Boolean): Student {
        val constructor = Student::class.primaryConstructor!!
        val parameterMap = emptyMap<KParameter, Any?>()
        val entity = constructor.callBy(parameterMap)
        entity.id = row[this.id]
        entity.name = row[this.name]!!
        entity.age = row[this.age]!!
        return entity
    }
    
    bug 
    opened by lookup-cat 4
  • generated code 怎么让IDEA 识别到?

    generated code 怎么让IDEA 识别到?

    我的配置

    id("com.google.devtools.ksp") version("1.6.21-1.0.5")
    
    //orm
    implementation("org.ktorm:ktorm-core:3.5.0")
    implementation("org.ktorm:ktorm-jackson:3.5.0")
    implementation("org.ktorm:ktorm-support-postgresql:3.5.0")
    implementation("org.ktorm:ktorm-ksp-api:1.0.0-RC3")
    ksp("org.ktorm:ktorm-ksp-compiler:1.0.0-RC3")
    
    kotlin {
    	sourceSets.main {
    		kotlin.srcDir("build/generated/ksp/main/kotlin")
    	}
    	sourceSets.test {
    		kotlin.srcDir("build/generated/ksp/test/kotlin")
    	}
    }
    

    自动生成的类确实是在build/generated/ksp/main/kotlin。但是这个类IDEA是识别不到的,在程序中引用不到。 image

    请问是我的配置有问题吗 还是?

    opened by jianweidai 2
  • 带泛型的 converter怎么写

    带泛型的 converter怎么写

    表里面有个json字段,类似这样

    interface Event : Entity<Event> {
        var id: Long
        var info: Map<String, *>
    }
    
    object Events : Table<Event>("events") {
        val id = long("id").primaryKey().bindTo { it.id }
        val info = json<Map<String, *>>("info").bindTo { it.info }
    }
    

    使用 ksp 该怎么写converter呢

    @org.ktorm.ksp.api.Table(tableName = "events")
    data class Event(
        @PrimaryKey
        var id: Long,
        @Column(converter = JsonTypeConvertor::class)
        var info: Map<String, *>
    )
    

    我试了

    object JsonTypeConvertor: SingleTypeConverter<Map<String, *>> {
        override fun convert(
            table: BaseTable<*>,
            columnName: String,
            propertyType: KClass<Map<String, *>>
        ): Column<Map<String, *>> = with(table) { json(columnName) }
    }
    

    以及

    object JsonTypeConvertor : MultiTypeConverter {
        override fun <T : Any> convert(
            table: BaseTable<*>,
            columnName: String,
            propertyType: KClass<T>
        ): Column<T> {
            return with(table) {
                varchar(columnName).transform(
                    { JsonUtil.deSerialize(it, propertyType.java)!! },
                    { JsonUtil.serialize(it) }
                )
            }
        }
    }
    

    自动生成的代码都是

    public object Events : BaseTable<Event>(tableName = "events",  entityClass = Event::class) {
        public val id: Column<Long> = long("id").primaryKey()
        public val info: Column<Map> = JsonTypeConvertor.convert(this,"info",Map::class)
    }
    

    而且会报错 public val info: Column<Map>: 2 type arguments expected for interface Map<K, out V>

    这该怎么写呢

    bug 
    opened by onXoot 2
  • 生成的代码中,没有将实体类字段名由驼峰转为下划线,导致对应不上数据库字段

    生成的代码中,没有将实体类字段名由驼峰转为下划线,导致对应不上数据库字段

    如题. 我必须这样命名才能对应上数据库字段 image image 当我这样命名时 image 生成的代码中的字段名没有转换为下划线模式 image

    版本关系 kotlin("jvm") version "1.7.22" id("com.google.devtools.ksp") version "1.7.22-1.0.8" implementation("org.ktorm:ktorm-core:3.5.0") org.ktorm:ktorm-ksp-api,org.ktorm:ktorm-ksp-compiler 1.0.0-RC3

    opened by openaddr 1
  • 美化生成的代码

    美化生成的代码

    主要修改点:

    • 给所有生成的代码加上了文档注释
    • 使用 facebook ktfmt 格式化代码,kotlinpoet 默认生成的代码有时候格式并不符合通用规范,有点丑
    • 重构 add & update 函数,增加 isDynamic 参数,支持插入/更新全量字段或仅非空字段
    • 删除 addAll & updateAll 函数,后续在另外的代码仓库中提供,同时删除了其对应的 ext 和 example 模块
    • 删除 checkIfSequenceModified 函数,改为在 add & update 函数中内联生成其函数体,在 api 包里面添加任何 public 成员都要慎重,尽量坚持最小化原则
    • 修改 copyright 年份
    • 其他代码格式的微调

    修改后,interface 实体类生成的代码如下:

    // Auto-generated by ktorm-ksp-compiler, DO NOT EDIT!
    package org.ktorm.ksp.example
    
    import java.time.LocalDate
    import kotlin.Int
    import kotlin.Long
    import kotlin.String
    import kotlin.Suppress
    import kotlin.UInt
    import org.ktorm.database.Database
    import org.ktorm.entity.Entity
    import org.ktorm.entity.EntitySequence
    import org.ktorm.entity.sequenceOf
    import org.ktorm.ksp.api.Undefined
    import org.ktorm.schema.Column
    import org.ktorm.schema.Table
    import org.ktorm.schema.date
    import org.ktorm.schema.int
    import org.ktorm.schema.long
    import org.ktorm.schema.varchar
    
    /**
     * Table employee. This is the kdoc for Employee.
     *
     * This is the second line.
     */
    public open class Employees(alias: String?) : Table<Employee>("employee", alias) {
        /**
         * Column id. This is the kdoc for id.
         *
         * This is the second line.
         */
        public val id: Column<Int> = int("id").primaryKey().bindTo { it.id }
    
        /** Column name. */
        public val name: Column<String> = varchar("name").bindTo { it.name }
    
        /** Column job. */
        public val job: Column<String> = varchar("job").bindTo { it.job }
    
        /** Column manager_id. */
        public val managerId: Column<Int> = int("manager_id").bindTo { it.managerId }
    
        /** Column hire_date. */
        public val hireDate: Column<LocalDate> = date("hire_date").bindTo { it.hireDate }
    
        /** Column salary. */
        public val salary: Column<Long> = long("salary").bindTo { it.salary }
    
        /** Column age. */
        public val age: Column<UInt> = registerColumn("age", UIntSqlType).bindTo { it.age }
    
        /** Column gender. */
        public val gender: Column<Gender> =
            registerColumn("gender", IntEnumSqlTypeFactory.createSqlType(Employee::gender)).bindTo { it.gender }
    
        /** Column department_id. */
        public val department: Column<Int> = int("department_id").references(Departments) { it.department }
    
        /**
         * Return a new-created table object with all properties (including the table name and columns and so on) being
         * copied from this table, but applying a new alias given by the parameter.
         */
        public override fun aliased(alias: String): Employees = Employees(alias)
    
        /** The default table object of employee. */
        public companion object : Employees(alias = null)
    }
    
    /** Return the default entity sequence of [Employees]. */
    public val Database.employees: EntitySequence<Employee, Employees>
        get() = this.sequenceOf(Employees)
    
    /**
     * Create an entity of [Employee] and specify the initial values for each properties, properties that doesn't have an
     * initial value will left unassigned.
     */
    @Suppress("FunctionName")
    public fun Employee(
        id: Int? = Undefined.of(),
        name: String? = Undefined.of(),
        job: String? = Undefined.of(),
        managerId: Int? = Undefined.of(),
        hireDate: LocalDate? = Undefined.of(),
        salary: Long? = Undefined.of(),
        age: UInt? = Undefined.of(),
        gender: Gender? = Undefined.of(),
        department: Department? = Undefined.of()
    ): Employee {
        val entity = Entity.create<Employee>()
        if (id !== Undefined.of<Int>()) {
            entity.id = id ?: error("`id` should not be null.")
        }
        if (name !== Undefined.of<String>()) {
            entity.name = name ?: error("`name` should not be null.")
        }
        if (job !== Undefined.of<String>()) {
            entity.job = job ?: error("`job` should not be null.")
        }
        if (managerId !== Undefined.of<Int>()) {
            entity.managerId = managerId
        }
        if (hireDate !== Undefined.of<LocalDate>()) {
            entity.hireDate = hireDate ?: error("`hireDate` should not be null.")
        }
        if (salary !== Undefined.of<Long>()) {
            entity.salary = salary ?: error("`salary` should not be null.")
        }
        if ((age as Any?) !== (Undefined.of<UInt>() as Any?)) {
            entity.age = age ?: error("`age` should not be null.")
        }
        if (gender !== Undefined.of<Gender>()) {
            entity.gender = gender
        }
        if (department !== Undefined.of<Department>()) {
            entity.department = department ?: error("`department` should not be null.")
        }
        return entity
    }
    
    /**
     * Return a deep copy of this entity (which has the same property values and tracked statuses), and alter the specified
     * property values.
     */
    public fun Employee.copy(
        id: Int? = Undefined.of(),
        name: String? = Undefined.of(),
        job: String? = Undefined.of(),
        managerId: Int? = Undefined.of(),
        hireDate: LocalDate? = Undefined.of(),
        salary: Long? = Undefined.of(),
        age: UInt? = Undefined.of(),
        gender: Gender? = Undefined.of(),
        department: Department? = Undefined.of()
    ): Employee {
        val entity = this.copy()
        if (id !== Undefined.of<Int>()) {
            entity.id = id ?: error("`id` should not be null.")
        }
        if (name !== Undefined.of<String>()) {
            entity.name = name ?: error("`name` should not be null.")
        }
        if (job !== Undefined.of<String>()) {
            entity.job = job ?: error("`job` should not be null.")
        }
        if (managerId !== Undefined.of<Int>()) {
            entity.managerId = managerId
        }
        if (hireDate !== Undefined.of<LocalDate>()) {
            entity.hireDate = hireDate ?: error("`hireDate` should not be null.")
        }
        if (salary !== Undefined.of<Long>()) {
            entity.salary = salary ?: error("`salary` should not be null.")
        }
        if ((age as Any?) !== (Undefined.of<UInt>() as Any?)) {
            entity.age = age ?: error("`age` should not be null.")
        }
        if (gender !== Undefined.of<Gender>()) {
            entity.gender = gender
        }
        if (department !== Undefined.of<Department>()) {
            entity.department = department ?: error("`department` should not be null.")
        }
        return entity
    }
    
    /** Return the value of [Employee.id]. */
    public operator fun Employee.component1(): Int = this.id
    
    /** Return the value of [Employee.name]. */
    public operator fun Employee.component2(): String = this.name
    
    /** Return the value of [Employee.job]. */
    public operator fun Employee.component3(): String = this.job
    
    /** Return the value of [Employee.managerId]. */
    public operator fun Employee.component4(): Int? = this.managerId
    
    /** Return the value of [Employee.hireDate]. */
    public operator fun Employee.component5(): LocalDate = this.hireDate
    
    /** Return the value of [Employee.salary]. */
    public operator fun Employee.component6(): Long = this.salary
    
    /** Return the value of [Employee.age]. */
    public operator fun Employee.component7(): UInt = this.age
    
    /** Return the value of [Employee.gender]. */
    public operator fun Employee.component8(): Gender? = this.gender
    
    /** Return the value of [Employee.department]. */
    public operator fun Employee.component9(): Department = this.department
    
    

    data class 实体类生成的代码如下:

    // Auto-generated by ktorm-ksp-compiler, DO NOT EDIT!
    package org.ktorm.ksp.example
    
    import kotlin.Boolean
    import kotlin.Int
    import kotlin.String
    import kotlin.Suppress
    import org.ktorm.database.Database
    import org.ktorm.dsl.QueryRowSet
    import org.ktorm.dsl.eq
    import org.ktorm.entity.EntitySequence
    import org.ktorm.entity.sequenceOf
    import org.ktorm.expression.ArgumentExpression
    import org.ktorm.expression.ColumnAssignmentExpression
    import org.ktorm.expression.ColumnExpression
    import org.ktorm.expression.InsertExpression
    import org.ktorm.expression.UpdateExpression
    import org.ktorm.schema.BaseTable
    import org.ktorm.schema.Column
    import org.ktorm.schema.SqlType
    import org.ktorm.schema.int
    import org.ktorm.schema.varchar
    
    /** Table student. */
    public open class Students(alias: String?) : BaseTable<Student>("student", alias) {
        /** Column id. */
        public val id: Column<Int> = int("id").primaryKey()
    
        /** Column name. */
        public val name: Column<String> = varchar("name")
    
        /** Column age. */
        public val age: Column<Int> = int("age")
    
        /**
         * Return a new-created table object with all properties (including the table name and columns and so on) being
         * copied from this table, but applying a new alias given by the parameter.
         */
        public override fun aliased(alias: String): Students = Students(alias)
    
        /** Create an entity object from the specific row of query results. */
        public override fun doCreateEntity(row: QueryRowSet, withReferences: Boolean): Student {
            return Student(id = row[id], name = row[name], age = row[age]!!)
        }
    
        /** The default table object of student. */
        public companion object : Students(alias = null)
    }
    
    /** Return the default entity sequence of [Students]. */
    public val Database.students: EntitySequence<Student, Students>
        get() = this.sequenceOf(Students)
    
    /**
     * Insert the given entity into this sequence and return the affected record number. If [isDynamic] is set to true, the
     * generated SQL will include only the non-null columns.
     *
     * Note that this function will obtain the generated key from the database and fill it into the corresponding property
     * after the insertion completes. But this requires us not to set the primary key’s value beforehand, otherwise, if you
     * do that, the given value will be inserted into the database, and no keys generated.
     */
    @Suppress("UNCHECKED_CAST")
    public fun EntitySequence<Student, Students>.add(entity: Student, isDynamic: Boolean = false): Int {
        val isModified =
            expression.where != null ||
                expression.groupBy.isNotEmpty() ||
                expression.having != null ||
                expression.isDistinct ||
                expression.orderBy.isNotEmpty() ||
                expression.offset != null ||
                expression.limit != null
    
        if (isModified) {
            val msg =
                "Entity manipulation functions are not supported by this sequence object. " +
                    "Please call on the origin sequence returned from database.sequenceOf(table)"
            throw UnsupportedOperationException(msg)
        }
    
        val assignments = LinkedHashMap<Column<*>, Any?>()
        if (isDynamic) {
            entity.id?.let { assignments[sourceTable.id] = it }
            entity.name?.let { assignments[sourceTable.name] = it }
            entity.age.let { assignments[sourceTable.age] = it }
        } else {
            entity.id?.let { assignments[sourceTable.id] = it }
            entity.name.let { assignments[sourceTable.name] = it }
            entity.age.let { assignments[sourceTable.age] = it }
        }
    
        if (assignments.isEmpty()) {
            return 0
        }
    
        val expression = // AliasRemover.visit(
            InsertExpression(
                table = sourceTable.asExpression(),
                assignments =
                    assignments.map { (col, argument) ->
                        ColumnAssignmentExpression(
                            column = col.asExpression() as ColumnExpression<Any>,
                            expression = ArgumentExpression(argument, col.sqlType as SqlType<Any>)
                        )
                    }
            )
        // )
    
        if (entity.id != null) {
            return database.executeUpdate(expression)
        } else {
            val (effects, rowSet) = database.executeUpdateAndRetrieveKeys(expression)
            if (rowSet.next()) {
                val generatedKey = sourceTable.id.sqlType.getResult(rowSet, 1)
                if (generatedKey != null) {
                    if (database.logger.isDebugEnabled()) {
                        database.logger.debug("Generated Key: $generatedKey")
                    }
    
                    entity.id = generatedKey
                }
            }
    
            return effects
        }
    }
    
    /**
     * Update the given entity to the database and return the affected record number. If [isDynamic] is set to true, the
     * generated SQL will include only the non-null columns.
     */
    @Suppress("UNCHECKED_CAST")
    public fun EntitySequence<Student, Students>.update(entity: Student, isDynamic: Boolean = false): Int {
        val isModified =
            expression.where != null ||
                expression.groupBy.isNotEmpty() ||
                expression.having != null ||
                expression.isDistinct ||
                expression.orderBy.isNotEmpty() ||
                expression.offset != null ||
                expression.limit != null
    
        if (isModified) {
            val msg =
                "Entity manipulation functions are not supported by this sequence object. " +
                    "Please call on the origin sequence returned from database.sequenceOf(table)"
            throw UnsupportedOperationException(msg)
        }
    
        val assignments = LinkedHashMap<Column<*>, Any?>()
        if (isDynamic) {
            entity.name?.let { assignments[sourceTable.name] = it }
            entity.age.let { assignments[sourceTable.age] = it }
        } else {
            entity.name.let { assignments[sourceTable.name] = it }
            entity.age.let { assignments[sourceTable.age] = it }
        }
    
        if (assignments.isEmpty()) {
            return 0
        }
    
        val conditions = sourceTable.id eq entity.id!!
        val expression = // AliasRemover.visit(
            UpdateExpression(
                table = sourceTable.asExpression(),
                assignments =
                    assignments.map { (col, argument) ->
                        ColumnAssignmentExpression(
                            column = col.asExpression() as ColumnExpression<Any>,
                            expression = ArgumentExpression(argument, col.sqlType as SqlType<Any>)
                        )
                    },
                where = conditions
            )
        // )
    
        return database.executeUpdate(expression)
    }
    
    
    opened by vincentlauvlwj 0
  • Undefined 重构

    Undefined 重构

    主要修改点:

    • 自己通过字节码生成子类,移除 bytebuddy 依赖,保持 Ktorm 的零依赖原则
    • Undefined.of<T>() 函数返回值改为可空类型,确保在使用基本类型和内联类型的时候,一定能编译为包装类型,避免拆箱
    • 生成的伪构造函数中,如果实体类的属性使用了内联类型,与 Undefiend.of<T>() 进行引用比较时会出现编译错误,转换为 Any? 再比较
    • 生成的伪构造函数中,如果实体类的属性为 val,也一样为它生成代码,只不过不能直接使用赋值语法,改为方括号,比如 entity[“id”] = id
    opened by vincentlauvlwj 0
  • [Feature] Support modify sequenceName

    [Feature] Support modify sequenceName

    现在当 table 名字首字母定义为小写的时候 (有时候希望table类名和数据库的表名完全一致,代码看起来更好理解) , 代码生成会报错

    // 这段代码生成出来会报错
    @Table(tableClassName = "t_user")
    data class User(
        @PrimaryKey
        var id: Int,
        var username: String,
        var age: Int
    )
    

    支持自定义 sequence 属性名字可以就可以修复这一点

    opened by zuisong 0
  • interface 注解遇到 `operator` 关键字生成错误

    interface 注解遇到 `operator` 关键字生成错误

    ktorm-ksp version: 1.0.0-RC3 ktorm version: 3.5.0

    code

    @org.ktorm.ksp.api.Table("test")
    interface TestOperator: org.ktorm.entity.Entity<TestOperator> {
        @org.ktorm.ksp.api.PrimaryKey
        var id: Long
        var operator: String
    }
    

    自动生成的代码

    @Suppress("FunctionName")
    public fun TestOperator(id: Long? = undefined(), operator_: String = undefined()): TestOperator {
        val entity = Entity.create<TestOperator>()
        if (id !== undefined<Long?>()) {
            entity.id = id ?: error("`entity` should not be null.")
        }
        if (operator !== undefined<String>()) {
            entity.operator = operator
        }
        return entity
    }
    
    public fun TestOperator.copy(id: Long? = undefined(), operator_: String = undefined()):
            TestOperator {
        val entity = this.copy()
        if (id !== undefined<Long?>()) {
            entity.id = id ?: error("`entity` should not be null.")
        }
        if (operator !== undefined<String>()) {
            entity.operator = operator
        }
        return entity
    }
    

    operator 在参数上自动加了个下划线,但是下面使用的时候没加 导致编译失败

    使用实体类形式可以编译成功

    bug 
    opened by onXoot 1
  • 提供一种注解和实体类分离的配置方式,  让实体类保持干净

    提供一种注解和实体类分离的配置方式, 让实体类保持干净

    目前的注解需要直接加在实体类上

    @Table
    data class User(
      @PrimaryKey
      val id: Int?,
      val username:String?,
      val age:Int?
    )
    

    也许我们可以提供另一种配置实体的方式, 就像下面这样

    @TableDefine(
      entityClass = User::class,
      primaryKeys = ["id"]
    )
    class EntityConfig
    

    尽管相比原本配置要稍加麻烦, 但由此一来, User类不再需要添加ktorm-ksp相关的注解. 这一点在实体类型需要作为api类库对外暴露时尤其有用, 例如构建一个小型的Kotlin Multiplatform项目.

    enhancement 
    opened by lookup-cat 1
Releases(1.0.0-RC3)
  • 1.0.0-RC3(Jul 21, 2022)

    • Support modify sequenceName by @zuisong in https://github.com/kotlin-orm/ktorm-ksp/pull/4
    • TableClass use open class instead of object by @zuisong in https://github.com/kotlin-orm/ktorm-ksp/pull/5
    • Support Interface Entity generation constructor components copy function #3
    • rename module name ktorm-ksp-ext-sequence-batch to ktorm-ksp-sequence-batch
    • Fix generic column bug #10
    • Fix local variable name conflict #6
    • Fix doCreateEntity function bug #7
    Source code(tar.gz)
    Source code(zip)
  • 1.0.0-RC2(Jun 28, 2022)

  • 1.0.0-RC(Jun 21, 2022)

Owner
KTORM.ORG
Kotlin ORM framework with strong-typed SQL DSL and sequence APIs.
KTORM.ORG
KSP extension for the kotlin-maven-plugin

kotlin-maven-symbol-processing Extension for the kotlin-maven-plugin to support Kotlin Symbol Processing (KSP). Usage To use this extension, add the d

Dyescape 19 Dec 18, 2022
BindsAdapter is an Android library to help you create and maintain Adapter class easier via ksp( Kotlin Symbol Processing).

BindsAdapter BindsAdapter is an Android library to help you create and maintain Adapter class easier via ksp( Kotlin Symbol Processing). Installation

Jintin 5 Jul 30, 2022
A collection of code generators powered by ksp.

AutoKsp A collection of code generators powered by ksp. status: working in progress Projects AutoGradlePlugin - Generate gradle plugin properties file

shenghaiyang 0 Nov 8, 2021
android webview loader using ksp

KSPWebViewLoader ?? @WebViewBuilder Annotation can be automating your webview settings. (WIP) How to use @WebViewBuilder( url = "https://www.googl

sehee Jeong 8 Apr 8, 2022
Exploring Kotlin Symbol Processing - KSP. This is just an experiment.

KSP example Exploring Kotlin Symbol Processing - KSP. This is just an experiment. Project contains 2 modules Processing Example Processing module is t

Merab Tato Kutalia 12 Aug 23, 2022
KSP annotation processor for Toothpick

toothpick-ksp KSP annotation processor for Toothpick. All credits go to olivierperez/ksp for the initial work on a KSP processor. Bear in mind this is

null 0 Oct 19, 2021
Kotlin Symbol Processing (KSP) sample project

Kotlin Symbol Processing (KSP) Sample Project Sample annotation processor created with Kotlin Symbol Processing (KSP) API. The repository supplements

Pavlo Stavytskyi 33 Dec 23, 2022
Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP)

Mockative Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP). Installation Mockative uses KSP to generate

Mockative 121 Dec 26, 2022
Ksp-di-library - Small library for DI in KMM apps

DI-KSP Small library for DI in KMM apps. Uses KSP for processing DI annotations:

Anna Zharkova 3 Feb 6, 2022
glide's ksp compiler ,use kotlin symbol processor

glide-ksp glide's ksp compiler ,use kotlin symbol processor requirements library version kotlin >= 1.6.10 ksp 1.6.10-1.0.2 usage add jitpack repositor

Mistletoe 24 Oct 17, 2022
KSP-based library that generates lists from your annotation usages

ListGen, Generate Lists From Functions That Have @Listed Annotations! Welcome to ListGen! ListGen is a KSP-based library that can generate lists (and

Adib Faramarzi 24 Dec 6, 2022
Used to generate the template code of GetX framework

Language: English | 中文简体 statement some fast code snippet prompt come from getx-snippets-intelliJ Description install Plugin effect Take a look at the

小呆呆666 242 Dec 30, 2022
A project aiming to generate KMP declarations from several library versions

kotlin-ketchup A project aiming to generate KMP declarations from several library versions LICENSE Apache 2.0. See ./LICENSE in this repository See th

Eugene Petrenko 4 Mar 1, 2022
A Kotlin library used to analyse discrete Markov chains, in order to generate plausible sequences

Markov Markov is a Kotlin library used to analyse discrete Markov chains, in order to generate plausible sequences. Using This project is still under

Xavier F. Gouchet 0 Nov 14, 2021
FizzBuzzKotlin - A function fizzBuzz to generate a list of string based on the input number

FizzBuzzKotlin write a function fizzBuzz to generate a list of string based on t

gson 0 Feb 12, 2022
A project that helps us generate the test project to test the Gradle plugin.

Ktlint Gradle Provides the function to generate a Gradle project for us to test your Gradle plugin Latest plugin version: [1.0.0] Table of content How

Jack Chen 5 Jul 20, 2022
🎲 Kotlin Symbol Processor to auto-generate extensive sealed classes and interfaces for Android and Kotlin.

SealedX ?? Kotlin Symbol Processor to auto-generate extensive sealed classes and interfaces for Android and Kotlin. Why SealedX? SealedX generates ext

Jaewoong Eum 236 Nov 30, 2022
KotlinScript that generate Reel from a given image, text and audio

ReelScriot KotlinScript that generate Reel from a given image, text and audio 80f4ea39-a7da-4f21-b0ff-7a17836a1cd0.mp4 6691b51d-d7a3-4915-ae41-8bec400

Chetan Gupta 2 Dec 6, 2022
An AutoValue extension that generates binary and source compatible equivalent Kotlin data classes of AutoValue models.

AutoValue Kotlin auto-value-kotlin (AVK) is an AutoValue extension that generates binary-and-source-compatible, equivalent Kotlin data classes. This i

Slack 19 Aug 5, 2022