Custom DSL

DSL

Create your own DSL just by inheriting Jpql class and adding your own functions.

Jpql has all the default DSL functions provided by Kotlin JDSL. You can use them to create your own functions. You can also create your own Model that implements Expression or Predicate and create a function to return this Model. You can implement JpqlSerializer to let Kotlin JDSL to render Model to String.

There are two ways to pass your own DSL to jpql(). The first is to implement JpqlDsl.Constructor as a companion object to create a DSL object, and the second is to create a DSL instance.

JpqlDsl.Constructor

With this way, you don't need to create an instance and a new instance is automatically created for each query creation.

class MyJpql : Jpql() {
    companion object Constructor : JpqlDsl.Constructor<MyJpql> {
        override fun newInstance(): MyJpql = MyJpql()
    }

    fun myFunction(value: String): Expression<String> {
        return function(String::class, "myFunction", listOf(value(value)))
    }

    fun Expressionable<String>.regexLike(value: String): Predicate {
        return MyRegexLike(this.toExpression(), value)
    }
}

val query = jpql(MyJpql) {
    select(
        entity(Book::class)
    ).from(
        entity(Book::class)
    ).where(
        myFunction("test").regexLike(".*")
    )
}

Jpql Instance

With this way, you can reuse a single instance for query creation and utilize dependency injection.

class MyJpql(
    private val encryptor: Encryptor,
) : Jpql() {
    fun Path<String>.equalToEncrypted(value: String): Predicate {
        val encrypted = encryptor.encrypt(value)
        return this.equal(encrypted)
    }
}

val encryptor = Encryptor()
val instance = MyJpql(encryptor)

val query = jpql(instance) {
    select(
        entity(Book::class)
    ).from(
        entity(Book::class)
    ).where(
        path(Book::title).equalToEncrypted("plain")
    )
}

Serializer

Implement JpqlSerializer and add it to RenderContext to render your own Model to String.

JpqlSerializer provides JpqlWriter and RenderContext for you to implement rendering. You can get JpqlRenderSerializer from RenderContext and use it to render other Model in your Model. You can also get JpqlRenderStatement and JpqlRenderClause from RenderContext, which let you know which statement and clause are currently being rendered. You can use them to render your Model as String.

class MyRegexLikeSerializer : JpqlSerializer<MyRegexLike> {
    override fun handledType(): KClass<MyRegexLike> {
        return MyRegexLike::class
    }

    override fun serialize(part: MyRegexLike, writer: JpqlWriter, context: RenderContext) {
        val delegate = context.getValue(JpqlRenderSerializer)

//        val statement = context.getValue(JpqlRenderStatement)
//        val clause = context.getValue(JpqlRenderClause)
//
//        statement.isSelect()
//        clause.isWhere()

        writer.write("REGEXP_LIKE")
        writer.writeParentheses {
            delegate.serialize(part.expr, writer, context)
            writer.write(", ")
            delegate.serialize(part.pattern, writer, context)
        }
    }
}

JpqlRenderContext provides registerModule() that allows you to register the JpqlSerializer implementation.

val myModule = object : JpqlRenderModule {
    override fun setupModule(context: JpqlRenderModule.SetupContext) {
        context.addSerializer(MyRegexLikeSerializer())
    }
}

val myContext = JpqlRenderContext().registerModule(myModule)

val jpqQuery = entityManager.createQuery(query, myContext)

Last updated