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

Docs: Extend entities definition docs and reference code from snippets #2264

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions documentation-website/Writerside/hi.tree
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
</toc-element>
<toc-element toc-title="Deep Dive into DAO">
<toc-element topic="DAO-Table-Types.topic"/>
<toc-element topic="DAO-Entity-definition.topic"/>
<toc-element topic="DAO-CRUD-Operations.topic"/>
<toc-element topic="DAO-Relationships.topic"/>
<toc-element topic="DAO-Field-Transformations.topic"/>
Expand Down
85 changes: 85 additions & 0 deletions documentation-website/Writerside/snippets/README.md
Original file line number Diff line number Diff line change
@@ -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
<!-- Include all file contents -->
<code-block lang="kotlin" src="exposed-dao/src/main/kotlin/org/example/StarWarsFilms.kt" />

<!-- Include specific symbol/s -->
<code-block lang="kotlin" src="exposed-dao/src/main/kotlin/org/example/StarWarsFilms.kt" include-symbol="MAX_VARCHAR_LENGTH, StarWarsFilmsTable" />
````

#### 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
<!-- Include specific lines -->
<code-block lang="sql" src="exposed-dao/src/main/kotlin/org/example/StarWarsFilms.kt" include-lines="9-13" />
```
#### 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() {
//...
}
```

25 changes: 25 additions & 0 deletions documentation-website/Writerside/snippets/exposed-dao/README.md
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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(StarWarsWFilmsWithRankTable)
}

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()
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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<String>) : Entity<String>(id) {
companion object : EntityClass<String, DirectorCustomEntity>(DirectorsCustomTable)

var name by DirectorsCustomTable.name
}
Original file line number Diff line number Diff line change
@@ -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<CompositeID>) : CompositeEntity(id) {
companion object : CompositeEntityClass<DirectorEntity>(DirectorsTable)

var genre by DirectorsTable.genre
}
Original file line number Diff line number Diff line change
@@ -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<Int>) : IntEntity(id) {
companion object : IntEntityClass<StarWarsFilmEntity>(StarWarsFilmsTable)

var sequelId by StarWarsFilmsTable.sequelId
var name by StarWarsFilmsTable.name
var director by StarWarsFilmsTable.director
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.example.entities

import org.example.tables.StarWarsWFilmsWithRankTable
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 StarWarsWFilmWithRankEntity(id: EntityID<Int>) : IntEntity(id) {
var sequelId by StarWarsWFilmsWithRankTable.sequelId
var name by StarWarsWFilmsWithRankTable.name
var rating by StarWarsWFilmsWithRankTable.rating

val rank: Long
get() = readValues[StarWarsWFilmsWithRankTable.rank]

companion object : IntEntityClass<StarWarsWFilmWithRankEntity>(StarWarsWFilmsWithRankTable) {
override fun searchQuery(op: Op<Boolean>): Query {
return super.searchQuery(op).adjustSelect {
select(columns + StarWarsWFilmsWithRankTable.rank)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bog-walk In the original example line 21 has .set in the end. I removed it to pass the detekt check because otherwise it produced the following error:
Type mismatch: inferred type is FieldSet but Query was expected
As it is now, the ReadExamples.readComputedField example (currently commented out) results in this error:

Exception in thread "main" java.lang.IllegalStateException: RANK() OVER( ORDER BY STARWARSWFILMSWITHRANK.RATING DESC) is not in record set
        at org.jetbrains.exposed.sql.ResultRow.getExpressionIndex(ResultRow.kt:128)
        at org.jetbrains.exposed.sql.ResultRow.getRaw(ResultRow.kt:109)
        at org.jetbrains.exposed.sql.ResultRow.getInternal$lambda$5(ResultRow.kt:72)
        at org.jetbrains.exposed.sql.ResultRow$ResultRowCache.cached(ResultRow.kt:207)
        at org.jetbrains.exposed.sql.ResultRow.getInternal(ResultRow.kt:71)
        at org.jetbrains.exposed.sql.ResultRow.get(ResultRow.kt:40)
        at org.example.entities.StarWarsWFilmWithRankEntity.getRank(StarWarsWFilmWithRankEntity.kt:16)
        at org.example.examples.ReadExamples.readComputedField(ReadExamples.kt:117)

Can you advise how to fix it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The entityCache.clear() from the original example is what's needed to fix this (or perform each operation in a separate transaction).

When new() creates an entity, it's cached. Then using find() first checks the cache for the entity and, only if it isn't found, then pings the database to find the record and returns it as a new entity.
So the current setup means the entity is in the cache, and is returned by find(). But it throws that exception because the cached entity only stores the data provided to new(). The window function rank is a database generated value so we need to make sure the entity is being retrieved from the database instead of the cache.

So we have to manually clear the cache since we're running these samples in a single transaction:

fun readComputedField() {
    transaction {
        StarWarsWFilmWithRankEntity.new {
            sequelId = MOVIE_SEQUELID
            name = "The Last Jedi"
            rating = MOVIE_RATING
        }

        entityCache.clear()

        StarWarsWFilmWithRankEntity
            .find { StarWarsWFilmsWithRankTable.name like "The%" }
            .map { it.name to it.rank }
    }
}

The alternative would be to simulate what a user more likely does, which is create entities in 1 transaction, then perform queries in a separate transaction:

// the way App.main() is set up, this would require changing the project's database config
Database.connect(
    "jdbc:h2:mem:test",
    "org.h2.Driver",
    databaseConfig = DatabaseConfig { useNestedTransactions = true }
)

fun readComputedField() {
    transaction {
        StarWarsWFilmWithRankEntity.new {
            sequelId = MOVIE_SEQUELID
            name = "The Last Jedi"
            rating = MOVIE_RATING
        }
    }

    transaction {
        StarWarsWFilmWithRankEntity
            .find { StarWarsWFilmsWithRankTable.name like "The%" }
            .map { it.name to it.rank }
    }
}

}
}
}
}
Original file line number Diff line number Diff line change
@@ -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<Int>) : IntEntity(id) {
companion object : IntEntityClass<UserEntity>(UsersTable)

var name by UsersTable.name
}
Original file line number Diff line number Diff line change
@@ -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<Int>) : IntEntity(id) {
companion object : IntEntityClass<UserRatingEntity>(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
}
Original file line number Diff line number Diff line change
@@ -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
}
}
}
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
Loading
Loading