From 3be1ecaa753dd6f176eb5cab064e3dc50e898b93 Mon Sep 17 00:00:00 2001 From: Vik Nikolova Date: Thu, 10 Oct 2024 09:22:51 +0200 Subject: [PATCH] Docs: Extend entities definition docs and reference code from snippets (#2264) * extend docs for defining entity instances and add links to the CRUD operations topic * extend exposed-dao code example and add README files * replace code blocks with references from code snippets and move the entity definition section * reference code from snippets in the DAO CRUD operations topic --- documentation-website/Writerside/hi.tree | 1 + .../Writerside/snippets/README.md | 85 ++++++++ .../Writerside/snippets/exposed-dao/README.md | 25 +++ .../src/main/kotlin/org/example/App.kt | 57 +++++- .../main/kotlin/org/example/StarWarsFilms.kt | 18 -- .../example/entities/DirectorCustomEntity.kt | 12 ++ .../org/example/entities/DirectorEntity.kt | 13 ++ .../example/entities/StarWarsFilmEntity.kt | 14 ++ .../entities/StarWarsFilmWithRankEntity.kt | 25 +++ .../kotlin/org/example/entities/UserEntity.kt | 12 ++ .../org/example/entities/UserRatingEntity.kt | 14 ++ .../org/example/examples/CreateExamples.kt | 42 ++++ .../org/example/examples/DeleteExamples.kt | 19 ++ .../org/example/examples/ReadExamples.kt | 126 ++++++++++++ .../org/example/examples/UpdateExamples.kt | 33 +++ .../kotlin/org/example/tables/CitiesTable.kt | 9 + .../example/tables/DirectorsCustomTable.kt | 15 ++ .../org/example/tables/DirectorsTable.kt | 15 ++ .../tables/DirectorsWithGuildRefTable.kt | 17 ++ .../kotlin/org/example/tables/GuildsTable.kt | 5 + .../org/example/tables/StarWarsFilmsTable.kt | 23 +++ .../tables/StarWarsFilmsWithRankTable.kt | 15 ++ .../org/example/tables/UserRatingsTable.kt | 9 + .../kotlin/org/example/tables/UsersTable.kt | 10 + .../topics/DAO-CRUD-Operations.topic | 188 +++++------------- .../topics/DAO-Entity-definition.topic | 50 +++++ .../Writerside/topics/DAO-Table-Types.topic | 117 +++++------ 27 files changed, 738 insertions(+), 231 deletions(-) create mode 100644 documentation-website/Writerside/snippets/README.md create mode 100644 documentation-website/Writerside/snippets/exposed-dao/README.md delete mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/StarWarsFilms.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/DirectorCustomEntity.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/DirectorEntity.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/StarWarsFilmEntity.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/StarWarsFilmWithRankEntity.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/UserEntity.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/UserRatingEntity.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/CreateExamples.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/DeleteExamples.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/ReadExamples.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/UpdateExamples.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/CitiesTable.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsCustomTable.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsTable.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsWithGuildRefTable.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/GuildsTable.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/StarWarsFilmsTable.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/StarWarsFilmsWithRankTable.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/UserRatingsTable.kt create mode 100644 documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/UsersTable.kt create mode 100644 documentation-website/Writerside/topics/DAO-Entity-definition.topic diff --git a/documentation-website/Writerside/hi.tree b/documentation-website/Writerside/hi.tree index 2a984611fa..2529375ba8 100644 --- a/documentation-website/Writerside/hi.tree +++ b/documentation-website/Writerside/hi.tree @@ -29,6 +29,7 @@ + diff --git a/documentation-website/Writerside/snippets/README.md b/documentation-website/Writerside/snippets/README.md new file mode 100644 index 0000000000..32b3f55935 --- /dev/null +++ b/documentation-website/Writerside/snippets/README.md @@ -0,0 +1,85 @@ +# Exposed Docs code examples + +The `snippets` folder contains a Gradle project with runnable code examples that show how to work with the Exposed library. +Code from these examples is referenced in corresponding documentation sections. + +## Run samples + +Each example has its own `README` file with instructions on how to run it. +To run an example, you can use a **run** Gradle task that depends on an example location. +For example, to run the `exposed-dao` example, open a new terminal window from the `snippets` folder and execute the following command: + +```bash +./gradlew :exposed-dao:run +``` + +Wait until IntelliJ IDEA builds and runs an example. + +## Reference code snippets + +### Reference files and symbols + +To display a specific source file in a topic, use the [`code-block`](https://www.jetbrains.com/help/writerside/semantic-markup-reference.html#code-block) +element with the `src` attribute. Whenever possible, reference the entire files or use the `include-symbol` attribute +to specify a class, method, or another symbol from the source file to include in the code block. + + +#### XML + +````xml + + + + + +```` + +#### Markdown + +```` +```kotlin +``` +{src="exposed-dao/src/main/kotlin/org/example/StarWarsFilms.kt"} +```` + +### Reference by line numbers + +In cases where the example code is not assigned or is commented out, use the `include-lines` attribute to specify the line +numbers from the source file to include in the code block: + +#### XML + +```xml + + +``` +#### Markdown + +```` +```kotlin +``` +{src="exposed-dao/src/main/kotlin/org/example/StarWarsFilms.kt" include-lines="9-13"} +```` + +When using this approach, be sure to document it for future reference by adding a note about the explicitly referenced +lines, as shown in the example below: + +```kotlin +/* + ... + + Important: The SQL query is referenced by line number in `TOPIC_NAME.topic`. + If you add, remove, or modify any lines before the SELECT statement, ensure you update the corresponding + line numbers in the `code-block` element of the referenced file. + + CREATE TABLE IF NOT EXISTS STARWARSFILMS + (ID INT AUTO_INCREMENT PRIMARY KEY, + SEQUEL_ID INT NOT NULL, + "name" VARCHAR(50) NOT NULL, + DIRECTOR VARCHAR(50) NOT NULL); + */ +object StarWarsFilmsTable : IntIdTable() { + //... +} +``` + diff --git a/documentation-website/Writerside/snippets/exposed-dao/README.md b/documentation-website/Writerside/snippets/exposed-dao/README.md new file mode 100644 index 0000000000..9eff5ab5d1 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/README.md @@ -0,0 +1,25 @@ +# Exposed DAO API examples + +A Gradle application that shows how to work with Exposed DAO API. +The files are referenced in the DAO's [CRUD operations](../../topics/DAO-CRUD-Operations.topic), +[Table types](../../topics/DAO-Table-Types.topic) and [Entity definition](../../topics/DAO-Entity-definition.topic) +topics. + +## Build + +To build the application, in a terminal window navigate to the `snippets` folder and run the following command: + +```shell +./gradlew :exposed-dao:build +``` + +## Run + +To run the application, in a terminal window navigate to the `snippets` folder and run the following command: + +```shell +./gradlew :exposed-dao:run +``` + +This will run queries to create new tables and run all functions in the `/examples` folder. +To only run a specific example, modify the `App.kt` file and re-run the project. diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/App.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/App.kt index e7e29adf2a..b8ffb0abb9 100644 --- a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/App.kt +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/App.kt @@ -1,13 +1,64 @@ package org.example -import org.jetbrains.exposed.sql.* +import org.example.examples.* +import org.example.tables.* +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.DatabaseConfig +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.StdOutSqlLogger +import org.jetbrains.exposed.sql.addLogger import org.jetbrains.exposed.sql.transactions.transaction fun main() { - Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver") + Database.connect( + "jdbc:h2:mem:test", + "org.h2.Driver", + databaseConfig = DatabaseConfig { useNestedTransactions = true } + ) transaction { addLogger(StdOutSqlLogger) - SchemaUtils.create(StarWarsFilms) + createTables() + runCreateExamples() + runReadExamples() + runUpdateExamples() + runDeleteExamples() } } + +fun createTables() { + SchemaUtils.create(StarWarsFilmsTable) + SchemaUtils.create(DirectorsTable) + SchemaUtils.create(UsersTable) + SchemaUtils.create(UserRatingsTable) + SchemaUtils.create(GuildsTable) + SchemaUtils.create(CitiesTable) + SchemaUtils.create(StarWarsFilmsWithRankTable) +} + +fun runCreateExamples() { + val createExamples = CreateExamples() + createExamples.createFilms() + createExamples.createNewWithCompositeId() +} + +fun runReadExamples() { + val readExamples = ReadExamples() + readExamples.readAll() + readExamples.readWithJoin() + readExamples.find() + readExamples.findByCompositeId() + readExamples.queriesAsExpressions() + readExamples.readComputedField() +} + +fun runUpdateExamples() { + val updateExamples = UpdateExamples() + updateExamples.updateFilms() + updateExamples.updateFilmProperty() +} + +fun runDeleteExamples() { + val deleteExamples = DeleteExamples() + deleteExamples.deleteFilm() +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/StarWarsFilms.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/StarWarsFilms.kt deleted file mode 100644 index f3fcd45232..0000000000 --- a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/StarWarsFilms.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.example - -import org.jetbrains.exposed.dao.id.IntIdTable - -const val MAX_VARCHAR_LENGTH = 50 - -/* -CREATE TABLE IF NOT EXISTS STARWARSFILMS -(ID INT AUTO_INCREMENT PRIMARY KEY, -SEQUEL_ID INT NOT NULL, -"name" VARCHAR(50) NOT NULL, -DIRECTOR VARCHAR(50) NOT NULL); -*/ -object StarWarsFilms : IntIdTable() { - val sequelId = integer("sequel_id").uniqueIndex() - val name = varchar("name", MAX_VARCHAR_LENGTH) - val director = varchar("director", MAX_VARCHAR_LENGTH) -} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/DirectorCustomEntity.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/DirectorCustomEntity.kt new file mode 100644 index 0000000000..2070503d6a --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/DirectorCustomEntity.kt @@ -0,0 +1,12 @@ +package org.example.entities + +import org.example.tables.DirectorsCustomTable +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID + +class DirectorCustomEntity(id: EntityID) : Entity(id) { + companion object : EntityClass(DirectorsCustomTable) + + var name by DirectorsCustomTable.name +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/DirectorEntity.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/DirectorEntity.kt new file mode 100644 index 0000000000..4d858a0f9a --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/DirectorEntity.kt @@ -0,0 +1,13 @@ +package org.example.entities + +import org.example.tables.DirectorsTable +import org.jetbrains.exposed.dao.CompositeEntity +import org.jetbrains.exposed.dao.CompositeEntityClass +import org.jetbrains.exposed.dao.id.CompositeID +import org.jetbrains.exposed.dao.id.EntityID + +class DirectorEntity(id: EntityID) : CompositeEntity(id) { + companion object : CompositeEntityClass(DirectorsTable) + + var genre by DirectorsTable.genre +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/StarWarsFilmEntity.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/StarWarsFilmEntity.kt new file mode 100644 index 0000000000..fae6592e43 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/StarWarsFilmEntity.kt @@ -0,0 +1,14 @@ +package org.example.entities + +import org.example.tables.StarWarsFilmsTable +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID + +class StarWarsFilmEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(StarWarsFilmsTable) + + var sequelId by StarWarsFilmsTable.sequelId + var name by StarWarsFilmsTable.name + var director by StarWarsFilmsTable.director +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/StarWarsFilmWithRankEntity.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/StarWarsFilmWithRankEntity.kt new file mode 100644 index 0000000000..85d9096bbe --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/StarWarsFilmWithRankEntity.kt @@ -0,0 +1,25 @@ +package org.example.entities + +import org.example.tables.StarWarsFilmsWithRankTable +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.Op +import org.jetbrains.exposed.sql.Query + +class StarWarsFilmWithRankEntity(id: EntityID) : IntEntity(id) { + var sequelId by StarWarsFilmsWithRankTable.sequelId + var name by StarWarsFilmsWithRankTable.name + var rating by StarWarsFilmsWithRankTable.rating + + val rank: Long + get() = readValues[StarWarsFilmsWithRankTable.rank] + + companion object : IntEntityClass(StarWarsFilmsWithRankTable) { + override fun searchQuery(op: Op): Query { + return super.searchQuery(op).adjustSelect { + select(columns + StarWarsFilmsWithRankTable.rank) + } + } + } +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/UserEntity.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/UserEntity.kt new file mode 100644 index 0000000000..c3f7ede64e --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/UserEntity.kt @@ -0,0 +1,12 @@ +package org.example.entities + +import org.example.tables.UsersTable +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID + +class UserEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(UsersTable) + + var name by UsersTable.name +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/UserRatingEntity.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/UserRatingEntity.kt new file mode 100644 index 0000000000..601c68c6b9 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/entities/UserRatingEntity.kt @@ -0,0 +1,14 @@ +package org.example.entities + +import org.example.tables.UserRatingsTable +import org.jetbrains.exposed.dao.IntEntity +import org.jetbrains.exposed.dao.IntEntityClass +import org.jetbrains.exposed.dao.id.EntityID + +class UserRatingEntity(id: EntityID) : IntEntity(id) { + companion object : IntEntityClass(UserRatingsTable) + + var value by UserRatingsTable.value + var film by StarWarsFilmEntity referencedOn UserRatingsTable.film // use referencedOn for normal references + var user by UserEntity referencedOn UserRatingsTable.user +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/CreateExamples.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/CreateExamples.kt new file mode 100644 index 0000000000..a87711d956 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/CreateExamples.kt @@ -0,0 +1,42 @@ +package org.example.examples + +import org.example.entities.DirectorEntity +import org.example.entities.StarWarsFilmEntity +import org.example.tables.DirectorsTable +import org.example.tables.Genre +import org.jetbrains.exposed.dao.id.CompositeID +import java.util.* + +const val MOVIE_SEQUEL_ID = 8 +const val MOVIE2_SEQUEL_ID = 9 + +class CreateExamples { + fun createFilms() { + val movie = StarWarsFilmEntity.new { + name = "The Last Jedi" + sequelId = MOVIE_SEQUEL_ID + director = "Rian Johnson" + } + println("Created a new record with name " + movie.name) + + // Create a new record with id + val movie2 = StarWarsFilmEntity.new(id = 2) { + name = "The Rise of Skywalker" + sequelId = MOVIE2_SEQUEL_ID + director = "J.J. Abrams" + } + println("Created a new record with id " + movie2.id) + } + + // Create a new record with a composite id + fun createNewWithCompositeId() { + val directorId = CompositeID { + it[DirectorsTable.name] = "J.J. Abrams" + it[DirectorsTable.guildId] = UUID.randomUUID() + } + + val director = DirectorEntity.new(directorId) { + genre = Genre.SCI_FI + } + } +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/DeleteExamples.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/DeleteExamples.kt new file mode 100644 index 0000000000..ef7420f92d --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/DeleteExamples.kt @@ -0,0 +1,19 @@ +package org.example.examples + +import org.example.entities.StarWarsFilmEntity + +class DeleteExamples { +/* + Delete a record. + + Important: The `movie.delete` statement is referenced by line number in `DAO-CRUD-operations.topic`. + If you add, remove, or modify any lines prior to this one, ensure you update the corresponding + line numbers in the `code-block` element of the referenced file. +*/ + fun deleteFilm() { + val movie = StarWarsFilmEntity.findById(2) + if (movie != null) { + movie.delete() + } + } +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/ReadExamples.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/ReadExamples.kt new file mode 100644 index 0000000000..e4670c52e0 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/ReadExamples.kt @@ -0,0 +1,126 @@ +package org.example.examples + +import org.example.entities.* +import org.example.tables.* +import org.jetbrains.exposed.dao.id.CompositeID +import org.jetbrains.exposed.sql.SortOrder +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.count +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.wrapAsExpression +import java.util.* + +const val MOVIE_SEQUELID = 8 +const val MIN_MOVIE_RATING = 5 +const val MOVIE_RATING = 4.2 + +class ReadExamples { + + fun readAll() { + // Read all movies + val allMovies = StarWarsFilmEntity.all() + allMovies.forEach({ println(it.name) }) + + // Sort results in ascending order + val moviesByAscOrder = StarWarsFilmEntity.all().sortedBy { it.sequelId } + moviesByAscOrder.map { println(it.sequelId) } + + // Sort results in descending order + val moviesByDescOrder = StarWarsFilmEntity.all().sortedByDescending { it.sequelId } + moviesByDescOrder.map { println(it.sequelId) } + } + + fun find() { + // Get an entity by its id value + val movie = StarWarsFilmEntity.findById(2) + + if (movie != null) { + // Read a property value + val movieName = movie.name + println("Created a new movie with name $movieName") + + // Read the id value + val movieId: Int = movie.id.value + println("The id of the new movie is $movieId") + } + + // Read all with a condition + val specificMovie = StarWarsFilmEntity.find { StarWarsFilmsTable.sequelId eq MOVIE_SEQUELID } + specificMovie.forEach({ println("Found a movie with sequelId " + MOVIE_SEQUELID + " and name " + it.name) }) + } + + // Read an entity with a join to another table + fun readWithJoin() { + val query = UsersTable.innerJoin(UserRatingsTable).innerJoin(StarWarsFilmsTable) + .select(UsersTable.columns) + .where { + StarWarsFilmsTable.sequelId eq MOVIE_SEQUELID and (UserRatingsTable.value greater MIN_MOVIE_RATING) + }.withDistinct() + + val users = UserEntity.wrapRows(query).toList() + users.map { println(it.name) } + } + + /* + Find records by composite id. + + Important: The SQL query is referenced by line number in `DAO-CRUD-operations.topic`. + If you add, remove, or modify any lines before the SELECT statement, ensure you update the corresponding + line numbers in the `code-block` element of the referenced file. + + SELECT DIRECTORS."name", DIRECTORS.GUILD_ID, DIRECTORS.GENRE + FROM DIRECTORS + WHERE (DIRECTORS."name" = 'J.J. Abrams') + AND (DIRECTORS.GUILD_ID = '2cc64f4f-1a2c-41ce-bda1-ee492f787f4b') + */ + fun findByCompositeId() { + val directorId = CompositeID { + it[DirectorsTable.name] = "J.J. Abrams" + it[DirectorsTable.guildId] = UUID.randomUUID() + } + + DirectorEntity.new(directorId) { + genre = Genre.SCI_FI + } + + val director = DirectorEntity.findById(directorId) + println("Found director $director") + val directors = DirectorEntity.find { DirectorsTable.id eq directorId } + directors.forEach({ println(it.genre) }) + } + + fun queriesAsExpressions() { + // Use a query as an expression to sort cities by the number of users in each city + CitiesTable.insert { + it[name] = "Amsterdam" + } + + val expression = wrapAsExpression( + UsersTable.select(UsersTable.id.count()) + .where { CitiesTable.id eq UsersTable.cityId } + ) + val cities = CitiesTable.selectAll() + .orderBy(expression, SortOrder.DESC) + .toList() + + cities.map { println(it[CitiesTable.name]) } + } + + fun readComputedField() { + transaction { + StarWarsFilmWithRankEntity.new { + sequelId = MOVIE_SEQUELID + name = "The Last Jedi" + rating = MOVIE_RATING + } + } + + transaction { + StarWarsFilmWithRankEntity + .find { StarWarsFilmsWithRankTable.name like "The%" } + .map { it.name to it.rank } + } + } +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/UpdateExamples.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/UpdateExamples.kt new file mode 100644 index 0000000000..d4ef3b8cf0 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/examples/UpdateExamples.kt @@ -0,0 +1,33 @@ +package org.example.examples + +import org.example.entities.StarWarsFilmEntity +import org.example.tables.StarWarsFilmsTable +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq + +class UpdateExamples { + fun updateFilmProperty() { + val movie = StarWarsFilmEntity.findById(2) + if (movie != null) { + /* + Important: The `movie.name` statement is referenced by line number in `DAO-CRUD-operations.topic`. + If you add, remove, or modify any lines prior to this one, ensure you update the corresponding + line numbers in the `code-block` element of the referenced file. + */ + movie.name = "Episode VIII – The Last Jedi" + println("The movie has been renamed to ${movie.name}") + } + } + fun updateFilms() { + // Find by id and update + val updatedMovie = StarWarsFilmEntity.findByIdAndUpdate(2) { + it.name = "Episode VIII – The Last Jedi" + } + println(updatedMovie?.name) + + // Find a single record by a condition and update + val updatedMovie2 = StarWarsFilmEntity.findSingleByAndUpdate(StarWarsFilmsTable.name eq "The Last Jedi") { + it.name = "Episode VIII – The Last Jedi" + } + println(updatedMovie2?.name) + } +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/CitiesTable.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/CitiesTable.kt new file mode 100644 index 0000000000..171dc956e3 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/CitiesTable.kt @@ -0,0 +1,9 @@ +package org.example.tables + +import org.jetbrains.exposed.dao.id.IntIdTable + +const val MAX_CITY_NAME_LENGTH = 50 + +object CitiesTable : IntIdTable() { + val name = varchar("name", MAX_CITY_NAME_LENGTH) +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsCustomTable.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsCustomTable.kt new file mode 100644 index 0000000000..d966dd5d47 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsCustomTable.kt @@ -0,0 +1,15 @@ +package org.example.tables + +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IdTable +import org.jetbrains.exposed.sql.Column + +const val MAX_ID_LENGTH = 32 +const val MAX_DIRECTOR_NAME_LENGTH = 32 + +object DirectorsCustomTable : IdTable() { + override val id: Column> = varchar("id", MAX_ID_LENGTH).entityId() + val name = varchar("name", MAX_DIRECTOR_NAME_LENGTH) + + override val primaryKey = PrimaryKey(id) +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsTable.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsTable.kt new file mode 100644 index 0000000000..7dc9a7357e --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsTable.kt @@ -0,0 +1,15 @@ +package org.example.tables + +import org.jetbrains.exposed.dao.id.CompositeIdTable + +enum class Genre { HORROR, DRAMA, THRILLER, SCI_FI } + +const val NAME_LENGTH = 50 + +object DirectorsTable : CompositeIdTable("directors") { + val name = varchar("name", NAME_LENGTH).entityId() + val guildId = uuid("guild_id").autoGenerate().entityId() + val genre = enumeration("genre") + + override val primaryKey = PrimaryKey(name, guildId) +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsWithGuildRefTable.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsWithGuildRefTable.kt new file mode 100644 index 0000000000..84e5d0b14b --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/DirectorsWithGuildRefTable.kt @@ -0,0 +1,17 @@ +package org.example.tables + +import org.jetbrains.exposed.dao.id.CompositeIdTable + +const val DIRECTOR_NAME_LENGTH = 50 + +object DirectorsWithGuildRefTable : CompositeIdTable() { + val name = varchar("name", DIRECTOR_NAME_LENGTH).entityId() + val guildId = reference("guild_id", GuildsTable) + val genre = enumeration("genre") + + init { + addIdColumn(guildId) + } + + override val primaryKey = PrimaryKey(name, guildId) +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/GuildsTable.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/GuildsTable.kt new file mode 100644 index 0000000000..7d946c6181 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/GuildsTable.kt @@ -0,0 +1,5 @@ +package org.example.tables + +import org.jetbrains.exposed.dao.id.UUIDTable + +object GuildsTable : UUIDTable("guilds") diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/StarWarsFilmsTable.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/StarWarsFilmsTable.kt new file mode 100644 index 0000000000..4c40e06e44 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/StarWarsFilmsTable.kt @@ -0,0 +1,23 @@ +package org.example.tables + +import org.jetbrains.exposed.dao.id.IntIdTable + +const val MAX_VARCHAR_LENGTH = 50 + +/* + Important: This file is referenced by line number in `DAO-Table-Types.topic`. + If you add, remove, or modify any lines, ensure you update the corresponding + line numbers in the `code-block` element of the referenced file. + + CREATE TABLE IF NOT EXISTS STARWARSFILMS + (ID INT AUTO_INCREMENT PRIMARY KEY, + SEQUEL_ID INT NOT NULL, + "name" VARCHAR(50) NOT NULL, + DIRECTOR VARCHAR(50) NOT NULL); +*/ + +object StarWarsFilmsTable : IntIdTable() { + val sequelId = integer("sequel_id").uniqueIndex() + val name = varchar("name", MAX_VARCHAR_LENGTH) + val director = varchar("director", MAX_VARCHAR_LENGTH) +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/StarWarsFilmsWithRankTable.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/StarWarsFilmsWithRankTable.kt new file mode 100644 index 0000000000..8e09e76bda --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/StarWarsFilmsWithRankTable.kt @@ -0,0 +1,15 @@ +package org.example.tables + +import org.jetbrains.exposed.dao.id.IntIdTable +import org.jetbrains.exposed.sql.Rank +import org.jetbrains.exposed.sql.SortOrder + +const val MAX_NAME_LENGTH = 32 + +object StarWarsFilmsWithRankTable : IntIdTable() { + val sequelId = integer("sequel_id").uniqueIndex() + val name = varchar("name", MAX_NAME_LENGTH) + val rating = double("rating") + + val rank = Rank().over().orderBy(rating, SortOrder.DESC) +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/UserRatingsTable.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/UserRatingsTable.kt new file mode 100644 index 0000000000..7d85903e9d --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/UserRatingsTable.kt @@ -0,0 +1,9 @@ +package org.example.tables + +import org.jetbrains.exposed.dao.id.IntIdTable + +object UserRatingsTable : IntIdTable() { + val value = long("value") + val film = reference("film", StarWarsFilmsTable) + val user = reference("user", UsersTable) +} diff --git a/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/UsersTable.kt b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/UsersTable.kt new file mode 100644 index 0000000000..05de541ff7 --- /dev/null +++ b/documentation-website/Writerside/snippets/exposed-dao/src/main/kotlin/org/example/tables/UsersTable.kt @@ -0,0 +1,10 @@ +package org.example.tables + +import org.jetbrains.exposed.dao.id.IntIdTable + +const val MAX_USER_NAME_LENGTH = 50 + +object UsersTable : IntIdTable() { + val name = varchar("name", MAX_USER_NAME_LENGTH) + val cityId = reference("cityId", CitiesTable.id) +} diff --git a/documentation-website/Writerside/topics/DAO-CRUD-Operations.topic b/documentation-website/Writerside/topics/DAO-CRUD-Operations.topic index 975a60c788..fa82797c42 100644 --- a/documentation-website/Writerside/topics/DAO-CRUD-Operations.topic +++ b/documentation-website/Writerside/topics/DAO-CRUD-Operations.topic @@ -3,144 +3,96 @@ SYSTEM "https://resources.jetbrains.com/writerside/1.0/xhtml-entities.dtd"> + title="CRUD operations" id="DAO-CRUD-Operations">

CRUD (Create, Read, Update, Delete) are the four basic operations supported by any database. This section demonstrates how to perform SQL CRUD operations using Exposed's DAO (Data Access Object) API.

+

+ These operations can be performed directly through the methods and properties of the Entity class + associated with the table. For more information, see . +

To create a new table row, use the new function on the entity class:

- - val movie = StarWarsFilm.new { - name = "The Last Jedi" - sequelId = 8 - director = "Rian Johnson" - } - +

- In the above example StarWarsFilm would be the entity instance linked to the StarWarsFilms table. - For more information, see . + In the above example StarWarsFilmEntity would be the entity instance linked to + the StarWarsFilmsTable table.

To provide a manual id value to a new entity, pass the value as an argument to the id parameter:

- - StarWarsFilm.new(id = 2) { - name = "The Rise of Skywalker" - sequelId = 9 - director = "J.J. Abrams" - } - +

If the entity is a CompositeEntity, the id value can be constructed by creating a component column-to-value association using CompositeID:

- - val newId = CompositeID { - it[Directors.name] = "J.J. Abrams" - it[Directors.guildId] = UUID.randomUUID() - } - - Director.new(newId) { - genre = Genre.SCI_FI - } - +

To read a value from a property, simply access it as you would with any property in a Kotlin class:

- - val name = movie.name - + An entity's id property is wrapped as an instance of the EntityID class. To access the actual wrapped value, for example the stored Int from a StarWarsFilm entity, use EntityID.value: - - val id: Int = movie.id.value - +

To retrieve entities, use one of the following methods:

To get all the entity instances associated with this entity class, use the all() function: - - val movies = StarWarsFilm.all() - + To get all the entity instances that conform to the conditional expression, use the find() function: - - val movies = StarWarsFilm.find { StarWarsFilms.sequelId eq 8 } - + To get an entity by its id value, use the findById() function: - - val movie = StarWarsFilm.findById(5) - + - - For a list of available predicates, see - DSL Where expression. -

If the entity is a CompositeEntity, its id property can be used to refer to all composite columns and to get entities, much like the id column of its associated CompositeIdTable:

- - - val directorId = CompositeID { - it[Directors.name] = "George Lucas" - it[Directors.guildId] = "..." - } - - // these will both deconstruct in SQL to the 2 component columns - val director = Director.findById(directorId) - val directors = Director.find { Directors.id eq directorId } - + +

+ The SQL query would result in something like the following: +

+ + + For a list of available predicates, see + DSL Where expression. +

Suppose that you want to find all users who rated the second Star Wars film with a score greater than 5. First, you would write that query using Exposed DSL.

- - val query = Users.innerJoin(UserRatings).innerJoin(StarWarsFilm) - .select(Users.columns) - .where { - StarWarsFilms.sequelId eq 2 and (UserRatings.value gt 5) - }.withDistinct() - +

Once the query is defined, you can wrap the result in the User entity using the wrapRows() function to generate entities from the retrieved data:

- - val users = User.wrapRows(query).toList() - +
-

- -

To sort results in ascending order, use sortedBy:

- - val movies = StarWarsFilm.all().sortedBy { it.sequelId } - +

To sort results in descending order, use sortedByDescending:

- - val movies = StarWarsFilm.all().sortedByDescending { it.sequelId } - +
@@ -148,9 +100,7 @@

You can update the value of a property just as you would with any property in a Kotlin class:

- - movie.name = "Episode VIII – The Last Jedi" - + Exposed doesn't make an immediate update when you set a new value for Entity, it just stores it on the inner map. "Flushing" values to the database occurs at the end of the transaction, or before the next SELECT * from the database. @@ -159,22 +109,14 @@

To search for an entity by its id and apply an update, use the findByIdAndUpdate() function:

- - val updatedMovie = StarWarsFilm.findByIdAndUpdate(5) { - it.name = "Episode VIII – The Last Jedi" - } - +

To search for a single entity by a query and apply an update, use the findSingleByAndUpdate() function:

- - val updatedMovie2 = StarWarsFilm.findSingleByAndUpdate(StarWarsFilms.name eq "The Last Jedi") { - it.name = "Episode VIII – The Last Jedi" - } - +
@@ -182,9 +124,7 @@

To delete a record, use the delete() function:

- - movie.delete() - + @@ -193,17 +133,7 @@ then order the result by that count.To do so, however, you need to convert the Query to an Expression. This can be done using the wrapAsExpression() function:

- - val expression = wrapAsExpression<Int>(Users - .select(Users.id.count()) - .where { - Cities.id eq Users.cityId - }) - val cities = Cities - .selectAll() - .orderBy(expression, SortOrder.DESC) - .toList() - +
@@ -211,46 +141,18 @@ the entity class can override any open function in EntityClass. However, to achieve this functionality, you only need to override searchQuery(). The results of the function can then be accessed through a property of the entity class:

- - ) : IntEntity(id) { - var sequelId by StarWarsFilms.sequelId - var name by StarWarsFilms.name - var rating by StarWarsFilms.rating - - val rank: Long - get() = readValues[StarWarsFilms.rank] - - companion object : IntEntityClass(StarWarsFilms) { - override fun searchQuery(op: Op): Query { - return super.searchQuery(op).adjustSelect { - select(columns + StarWarsFilms.rank).set - } - } - } - } - - transaction { - StarWarsFilm.new { - sequelId = 8 - name = "The Last Jedi" - rating = 4.2 - } - // more insertions ... - entityCache.clear() - - // fetch entities with value (or store entities then read value) - StarWarsFilm.find { StarWarsFilms.name like "The%" }.map { it.name to it.rank } - } - ]]> + + + + + + + + +

+ Then, creating and fetching entities would look like this: +

+
diff --git a/documentation-website/Writerside/topics/DAO-Entity-definition.topic b/documentation-website/Writerside/topics/DAO-Entity-definition.topic new file mode 100644 index 0000000000..0f699044c7 --- /dev/null +++ b/documentation-website/Writerside/topics/DAO-Entity-definition.topic @@ -0,0 +1,50 @@ + + + + +

+ Representing database tables as Kotlin objects ensures type safety and allows you to work with database + records just like regular Kotlin objects, taking full advantage of Kotlin's language features. +

+

+ When using the DAO approach, IdTable needs to be associated with an Entity, because every database record + in this table needs to be mapped to an Entity instance, identified by its primary key. +

+

+ An entity instance is defined as a class. + In the following example, StarWarsFilmEntity is the entity class linked to the table StarWarsFilmsTable: +

+ + + + + + + + + +
  • + Since StarWarsFilmsTable is an IntIdTable, the StarWarsFilmsEntity class extends from IntEntity, + which indicates that the id and primary key of StarWarsFilmsTable is of type Int. +
  • +
  • + The companion object block defines an EntityClass which is responsible for maintaining + the relation between the StarWarsFilmsEntity class and the actual table object, StarWarsFilmsTable. +
  • +
  • + Each column in the table is represented as a property in the class, where the by keyword + ensures the data is fetched or updated from the corresponding column when accessed. +
  • +
    +

    + Once the entity class is defined, instances of this class allow you to manipulate individual records + from the corresponding table. This could involve + creating a new record, + retrieving a row based on its primary key, + updating values, or + deleting records. +

    +
    diff --git a/documentation-website/Writerside/topics/DAO-Table-Types.topic b/documentation-website/Writerside/topics/DAO-Table-Types.topic index 4c6a91aef7..b317bc66b1 100644 --- a/documentation-website/Writerside/topics/DAO-Table-Types.topic +++ b/documentation-website/Writerside/topics/DAO-Table-Types.topic @@ -6,7 +6,8 @@ title="Table types" id="DAO-Table-Types" help-id="DAO-Table-types"> - + + Auto-incrementing <code>id</code> column tables

    Apart from the core Table class, Exposed provides the base @@ -50,7 +51,8 @@

    The following example represents a table with custom columns sequel_id, name, and director:

    - +

    The IntIdTable class automatically generates an auto-incrementing integer id @@ -59,83 +61,64 @@ When the table is created, it corresponds to the following SQL query:

    - +

    For more information on defining and configuring tables in Exposed, see .

    - -

    - An entity instance or a row in the table is defined as a class instance: -

    - ) : IntEntity(id) { - companion object : IntEntityClass(StarWarsFilms) - - var sequelId by StarWarsFilms.sequelId - var name by StarWarsFilms.name - var director by StarWarsFilms.director - } - ]]> -
    -

    To define multiple columns as part of the primary key and id, use CompositeIdTable and mark each composite column using - entityId(). - Each component column will be available for CRUD operations either individually (as for any standard column) or all together as part of the - id column:

    - - ("genre") - - override val primaryKey = PrimaryKey(name, guildId) - } - - class Director(id: EntityID) : CompositeEntity(id) { - companion object : CompositeEntityClass(Directors) +

    + To define multiple columns as part of the primary key and id, use + CompositeIdTable + and mark each composite column using entityId(). + Each component column will be available for CRUD operations either individually (as for any standard column) + or all together as part of the id column: +

    + + + + + + + + - var genre by Directors.genre - } - ]]>
    - ("genre") - - init { - addIdColumn(guildId) - } - - override val primaryKey = PrimaryKey(name, guildId) - } - ]]> + + + + + + + + -

    To define a custom column type as the primary key and id, use a typed IdTable directly and override the id column:

    - - ("directors") { - override val id: Column> = varchar("id", 32).entityId() - val name = varchar("name", 50) - - override val primaryKey = PrimaryKey(id) - } - - class Director(id: EntityID) : Entity(id) { - companion object : EntityClass(Directors) - - var name by Directors.name - } - ]]> +

    + To define a custom column type as the primary key and id, use a typed IdTable directly and + override the id column, as shown in the following example: +

    + + + + + + + + +

    + In the definition of DirectorsCustomTable, the id field is of type Column<EntityID<String>>, + which will hold String values with a length of up to 32 characters. Using the + override keyword indicates that this id column is overriding the default + id column provided by IdTable. +

    +

    + Once all columns are defined, the id column is explicitly set as the primary key for the table, + using the override keyword once again. +