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

Mapping UUID for strong type, column mapping feature? #1691

Open
Coneys opened this issue Feb 25, 2023 · 0 comments
Open

Mapping UUID for strong type, column mapping feature? #1691

Coneys opened this issue Feb 25, 2023 · 0 comments

Comments

@Coneys
Copy link

Coneys commented Feb 25, 2023

Background and issue to resolve:

Hello! In our project we are using UUID for almost all entities. We would like to save and retrieve them directly from Exposed, with compilator support (strongly typed). Our "UUID" classes are shared between backend, iOS, Android and JS, so we have to keep them "clean" in only common code

Expectations
We would like to set some kind of annotation / parameter to column, that would allow us to create mapping between Type(binary uuid) stored in Database and Type used in application (some kind of class)

Below I will provide my custom resolution of this issue. Please let me know if it is correct approach, or should I use some other functionality of Exposed.

Prototype
I had no idea where to start, because I could not find any information about column mapping. I created custom column, that internally uses UUIDColumnType:

class TypedUUIDColumnType<T : UUIDType>(private val factory: UUIDTypeFactory<T>) : ColumnType() {

    private val uuidColumnType = UUIDColumnType()
    override fun sqlType(): String = uuidColumnType.sqlType()

    override fun valueFromDB(value: Any): T {
        val uuid = uuidColumnType.valueFromDB(value)
        val stringRepresentation = uuid.toString()
        return factory.createFrom(stringRepresentation)
    }

    override fun notNullValueToDB(value: Any): Any {
        return when (value) {
            is String -> uuidColumnType.notNullValueToDB(UUID.fromString(value))
            else -> {
                val asString = (value as T).asString()
                uuidColumnType.notNullValueToDB(UUID.fromString(asString))
            }
        }
    }

    override fun nonNullValueToString(value: Any): String {
        return (value as T).asString()
    }

    override fun readObject(rs: ResultSet, index: Int): Any? {
        return uuidColumnType.readObject(rs, index)
    }

}


fun <T : UUIDType> Table.typedUUID(name: String, factory: UUIDTypeFactory<T>): Column<T> =
    registerColumn(name, TypedUUIDColumnType(factory))

Factory itself is simple functional interface, to make sure that all our custom UUID Types can be created from plain string

fun interface UUIDTypeFactory<T : UUIDType> {
    fun createFrom(stringId: String): T
} 

Abstract UUID Type just implements Comparable interface and hides rawValue of String:

abstract class UUIDType() : Comparable<UUIDType> {
    protected abstract val rawValue: String
    fun asString() = rawValue
    override fun toString(): String = rawValue
    override fun compareTo(other: UUIDType): Int {
        return rawValue.compareTo(other.rawValue)
    }
}

Usage

data class SomeId(override val rawValue: String) : UUIDType() {
    companion object : UUIDTypeFactory<SomeId> {
        override fun createFrom(stringId: String) = SomeId(stringId)
    }
}

object SomeTable : Table() {
    val uuid = typedUUID("uuid", SomeId)
}

fun example(newId: SomeId) {
    SomeTable.insert {
        it[uuid] = newId
    }
}

fun retrieveExample():SomeId{
    return SomeTable
        .selectAll()
        .first()
        .run { this[SomeTable.uuid] }
}


Conclusion
Usage is pretty convenient, but I'm not sure if I could run in some issues with other DBs or should I be worry about efficiency of this solution

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

No branches or pull requests

1 participant