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.
This library is highly experimental now. The APIs are subject to change, there are currently no tests (its usability is guaranteed by our internal consuming projects though), and please expect bugs and report them.
"com.huanshankeji:exposed-gadt-mapping:$libraryVersion"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.
Please note that these APIs are far from stable. There are going to be refactors in future releases.
Check out the API documentation here.
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>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.
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)
)
)
)
)
)
}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()