Skip to content

Repository files navigation

Exposed GADT mapping

Maven Central

A library based on Exposed DSL providing mappings between data entities and tables with support for GADT (generalized algebraic data type), aka features including nested properties of composite class types, type parameters and their type inference, and sealed classes

This project is an attempt to provide an alternative to Exposed DAO while supporting some more advanced functional programming features. See JetBrains/Exposed#24 for more details.

Maintenance status

This library is no longer actively maintained. GADT-style Exposed mappings are no tnat necessary now that AI agents can generate mapping code ad hoc for each schema and entity model. The repository and Maven artifacts remain available for existing users (including the optional crud-with-mapper module in exposed-vertx-sql-client), but expect no new features or routine fixes.

Highly experimental

This library is highly experimental. The APIs are subject to change, there are currently no tests, and please expect bugs and report them if you still use it.

Add to your dependencies

The Maven coordinate

"com.huanshankeji:exposed-gadt-mapping:$libraryVersion"

Important note : compatibility with Exposed

If you encounter issues likely caused by compatibility with Exposed, please try using the same version of Exposed this library depends on. The current Exposed version for v0.4.0 of this library is v1.0.0-rc-2.

Basic usage guide

Please note that these APIs are far from stable. There are going to be refactors in future releases.

Check out the API documentation here.

Table and data definitions

Tables and joins

typealias DirectorId = Int

class Director(val directorId: DirectorId, val name: String)

class FilmDetails<DirectorT>(
    val sequelId: Int,
    val name: String,
    val director: DirectorT
)
typealias FilmDetailsWithDirectorId = FilmDetails<DirectorId>

typealias FilmId = Int

class Film<DirectorT>(val filmId: FilmId, val filmDetails: FilmDetails<DirectorT>)
typealias FilmWithDirectorId = Film<DirectorId>
typealias FullFilm = Film<Director>

Data entities and attributes

typealias DirectorId = Int

class Director(val directorId: DirectorId, val name: String)

class FilmDetails<DirectorT>(
    val sequelId: Int,
    val name: String,
    val director: DirectorT
)
typealias FilmDetailsWithDirectorId = FilmDetails<DirectorId>

typealias FilmId = Int

class Film<DirectorT>(val filmId: FilmId, val filmDetails: FilmDetails<DirectorT>)
typealias FilmWithDirectorId = Film<DirectorId>
typealias FullFilm = Film<Director>

A nested composite class property can either map to flattened fields or a table referenced by a foreign key: FilmDetails is a nested class in Film, but the corresponding table Films has the FilmDetails members/fields flattened directly instead of referencing a corresponding table for FilmDetails with a foreign key; on the contrary, a director : Director member of FilmDetails<Director> maps to the Directors table referenced.

As laid out above in the code, a recommended approach to define data types is to make necessary use of type parameters to improve code reuse.

Create mappers

You can create mappers with the overloaded reflectionBasedClassPropertyDataMapper functions. Pass the propertyColumnMappingConfigMapOverride parameter to override the default options.

object Mappers {
    val director = reflectionBasedClassPropertyDataMapper<Director>(Directors)
    val filmDetailsWithDirectorId = reflectionBasedClassPropertyDataMapper<FilmDetailsWithDirectorId>(
        Films,
        propertyColumnMappingConfigMapOverride = mapOf(
            // The default name is the property name "director", but there is no column property with such a name, therefore we need to pass a custom name.
            FilmDetailsWithDirectorId::director to PropertyColumnMappingConfig.create<DirectorId>(columnPropertyName = Films::directorId.name)
        )
    )
    val filmWithDirectorId = reflectionBasedClassPropertyDataMapper<FilmWithDirectorId>(
        Films,
        propertyColumnMappingConfigMapOverride = mapOf(
            FilmWithDirectorId::filmDetails to PropertyColumnMappingConfig.create<FilmDetailsWithDirectorId>(
                // You can pass a nested custom mapper.
                customMapper = filmDetailsWithDirectorId
            )
        )
    )
    val fullFilm = reflectionBasedClassPropertyDataMapper<FullFilm>(
        filmsLeftJoinDirectors,
        propertyColumnMappingConfigMapOverride = mapOf(
            FullFilm::filmDetails to PropertyColumnMappingConfig.create(
                adt = PropertyColumnMappingConfig.Adt.Product(
                    mapOf(
                        // Because `name` is a duplicate name column so a custom mapper has to be passed here, otherwise the `CHOOSE_FIRST` option maps the data property `Director::name` to the wrong column `Films::name`.
                        FilmDetails<Director>::director to PropertyColumnMappingConfig.create<Director>(customMapper = director)
                    )
                )
            )
        )
    )
}

CRUD operations

Call updateBuilderSetter to get a setter lambda to pass to insert or update. Call selectWithMapper to execute a query with a mapper.

val directorId = 1
val directorDetails = DirectorDetails("George Lucas")
Directors.insert(Mappers.directorDetails.updateBuilderSetter(directorDetails))

val episodeIFilmDetails = FilmDetails(1, "Star Wars: Episode I – The Phantom Menace", directorId)
Films.insert(Mappers.filmDetailsWithDirectorId.updateBuilderSetter(episodeIFilmDetails)) // insert without the ID since it's `AUTO_INCREMENT`

val filmId = 2
val episodeIIFilmDetails = FilmDetails(2, "Star Wars: Episode II – Attack of the Clones", directorId)
val filmWithDirectorId = FilmWithDirectorId(filmId, episodeIIFilmDetails)
if (dialectSupportsIdentityInsert)
    Films.insert(Mappers.filmWithDirectorId.updateBuilderSetter(filmWithDirectorId)) // insert with the ID
else
    Films.insert(Mappers.filmDetailsWithDirectorId.updateBuilderSetter(episodeIIFilmDetails))

val fullFilm = with(Mappers.fullFilm) {
    resultRowToData(filmsLeftJoinDirectors.select(neededColumns).where(Films.filmId eq filmId).single())
}
val fullFilms =
    filmsLeftJoinDirectors.selectWithMapper(Mappers.fullFilm, Films.filmId inList listOf(1, 2)).toList()

About

A library based on Exposed DSL providing mappings between data entities and tables with support for GADT (generalized algebraic data type), aka features including nested properties of composite class types, type parameters, and sealed classes

Resources

Code of conduct

Contributing

Stars

3 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages