Skip to content

Commit

Permalink
docs: EXPOSED-684 Add documentation for exec() and move transaction d…
Browse files Browse the repository at this point in the history
…ocs to new element

- Move Transactions.md to a new top-level element (Transactions)
- Separate section on statement interceptors to its own topic
- Create new topic about working with SQL strings with exec()
- Put example code for new topic in new exposed-transactions snippets project
  • Loading branch information
bog-walk committed Jan 31, 2025
1 parent f74a7b0 commit ebf2670
Show file tree
Hide file tree
Showing 15 changed files with 456 additions and 42 deletions.
6 changes: 5 additions & 1 deletion documentation-website/Writerside/hi.tree
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,18 @@
<toc-element toc-title="Databases">
<toc-element topic="Working-with-Database.md"/>
<toc-element topic="Working-with-DataSource.md"/>
<toc-element topic="Transactions.md"/>
</toc-element>
<toc-element toc-title="Schemas">
<toc-element topic="Working-with-Tables.topic"/>
<toc-element topic="Data-Types.topic"/>
<toc-element topic="SQL-Functions.md"/>
<toc-element topic="Working-with-Schema.topic"/>
</toc-element>
<toc-element toc-title="Transactions">
<toc-element topic="Transactions.md"/>
<toc-element topic="Working-with-SQL-Strings.md"/>
<toc-element topic="Statement-Interceptors.md"/>
</toc-element>
<toc-element toc-title="Deep Dive into DSL">
<toc-element topic="DSL-Table-Types.topic"/>
<toc-element topic="DSL-Joining-tables.topic"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Exposed SQL Functions examples

A Gradle application that shows how to perform units of work with database transactions using Exposed API.
The files are referenced in the Transactions's [Working with SQL Strings](../../topics/Working-with-SQL-Strings.md) topic.

## Prerequisites

The project contains examples that run against H2 and MySQL databases. While H2 makes use of
in-memory storage, in order to run queries against MySQL, you must first install MySQL and create a local database.

To learn how to install MySQL, see the [installation guide](https://dev.mysql.com/doc/refman/8.4/en/installing.html).

## Database configuration

All database connections are configured within the `App.kt` file located in `src/main/kotlin/org/example/`.
You might want to adjust the MySQL database configuration to match your local setup.

```kotlin
val mysqlDb = Database.connect(
"jdbc:mysql://localhost:3306/test",
driver = "com.mysql.cj.jdbc.Driver",
user = "root",
password = "password"
)
```

## Build

To build the application, in a terminal window navigate to the `snippets` folder and run the following command:

```shell
./gradlew :exposed-sql-functions:build
```

## Run

To run the application, in a terminal window navigate to the `snippets` folder and run the following command:

```shell
./gradlew :exposed-sql-functions: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
@@ -0,0 +1,47 @@
plugins {
// Apply the org.jetbrains.kotlin.jvm Plugin to add support for Kotlin.
alias(libs.plugins.jvm)

// Apply the application plugin to add support for building a CLI application in Java.
application
}

repositories {
// Use Maven Central for resolving dependencies.
mavenCentral()
}

dependencies {
// Use the Kotlin JUnit 5 integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")

// Use the JUnit 5 integration.
testImplementation(libs.junit.jupiter.engine)

testRuntimeOnly("org.junit.platform:junit-platform-launcher")

// This dependency is used by the application.
implementation(libs.guava)
implementation(libs.exposed.core)
implementation(libs.exposed.jdbc)
implementation(libs.h2)
implementation("mysql:mysql-connector-java:8.0.33")
implementation(libs.slf4j)
}

// Apply a specific Java toolchain to ease working on different environments.
java {
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}

application {
// Define the main class for the application.
mainClass = "org.example.AppKt"
}

tasks.named<Test>("test") {
// Use JUnit Platform for unit tests.
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package org.example

import org.example.examples.ExecExamples
import org.example.examples.ExecMySQLExamples
import org.example.tables.FilmsTable
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() {
val h2Db = Database.connect(
"jdbc:h2:mem:test",
"org.h2.Driver",
databaseConfig = DatabaseConfig { useNestedTransactions = true }
)

val mysqlDb = Database.connect(
"jdbc:mysql://localhost:3306/test?allowMultiQueries=true",
driver = "com.mysql.cj.jdbc.Driver",
user = "root",
password = "password",
)

transaction(h2Db) {
addLogger(StdOutSqlLogger)
SchemaUtils.create(FilmsTable)
runExecExamples()
}

transaction(mysqlDb) {
addLogger(StdOutSqlLogger)
SchemaUtils.create(FilmsTable)
runExecMySQLExamples()
}
}

fun runExecExamples() {
val execExamples = ExecExamples()
execExamples.execBasicStrings()
execExamples.execAndMapResult()
execExamples.execWithParameters()
execExamples.execWithTypeOverride()
}

fun runExecMySQLExamples() {
val execMySQLExamples = ExecMySQLExamples()
execMySQLExamples.execMultipleStrings()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.example.examples

import org.jetbrains.exposed.sql.transactions.TransactionManager
import java.sql.ResultSet

fun <T : Any> String.execAndMap(transform: (ResultSet) -> T): List<T> {
val result = mutableListOf<T>()
TransactionManager.current().exec(this) { rs ->
while (rs.next()) {
result += transform(rs)
}
}
return result
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package org.example.examples

import org.example.tables.FilmsTable
import org.jetbrains.exposed.sql.BooleanColumnType
import org.jetbrains.exposed.sql.DoubleColumnType
import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.statements.StatementType
import org.jetbrains.exposed.sql.transactions.transaction

/*
Important: This file is referenced by line number in `Working-with-SQL-Strings.md`.
If you add, remove, or modify any lines, ensure you update the corresponding
line numbers in the `code-block` element of the referenced file.
*/

private const val GOOD_RATING = 9.0
private const val BAD_RATING = 2.1

class ExecExamples {
fun execBasicStrings() {
transaction {
addLogger(StdOutSqlLogger)

val secretCode = "abc"
exec("CREATE USER IF NOT EXISTS GUEST PASSWORD '$secretCode'")
exec("GRANT ALL PRIVILEGES ON ${FilmsTable.nameInDatabaseCase()} TO GUEST")

val version = exec("SELECT H2VERSION()") { result ->
result.next()
result.getString(1)
}
println(version)

val schema = "TABLE_SCHEMA"
val name = "TABLE_NAME"
val rowCount = "ROW_COUNT_ESTIMATE"
val tableInfo = exec("SELECT $schema, $name, $rowCount FROM INFORMATION_SCHEMA.TABLES") { result ->
val info = mutableListOf<Triple<String, String, Int>>()
while (result.next()) {
info += Triple(result.getString(schema), result.getString(name), result.getInt(rowCount))
}
info
} ?: emptyList()
println(tableInfo.last())

exec(
stmt = "DROP USER IF EXISTS GUEST",
explicitStatementType = StatementType.DROP
)
}
}

fun execAndMapResult() {
transaction {
addLogger(StdOutSqlLogger)

FilmsTable.insert {
it[title] = "The Good Film"
it[rating] = GOOD_RATING
it[nominated] = true
}
FilmsTable.insert {
it[title] = "The Bad Film"
it[rating] = BAD_RATING
it[nominated] = false
}

val toIgnore = "SELECT FILMS.ID, FILMS.TITLE FROM FILMS WHERE FILMS.RATING <= 3.0".execAndMap { result ->
result.getInt("FILMS.ID") to result.getString("FILMS.TITLE")
}
println(toIgnore)
}
}

fun execWithParameters() {
transaction {
addLogger(StdOutSqlLogger)

val toWatch = exec(
stmt = "SELECT FILMS.ID, FILMS.TITLE FROM FILMS WHERE (FILMS.NOMINATED = ?) AND (FILMS.RATING >= ?)",
args = listOf(BooleanColumnType() to true, DoubleColumnType() to GOOD_RATING)
) { result ->
val films = mutableListOf<Pair<Int, String>>()
while (result.next()) {
films += result.getInt(1) to result.getString(2)
}
films
}
println(toWatch)
}
}

fun execWithTypeOverride() {
transaction {
addLogger(StdOutSqlLogger)

val plan = exec(
stmt = "EXPLAIN SELECT * FROM FILMS WHERE FILMS.ID = 1",
explicitStatementType = StatementType.EXEC
) { result ->
val data = mutableListOf<Pair<String, Any?>>()
while (result.next()) {
repeat(result.metaData.columnCount) {
data += result.metaData.getColumnName(it + 1) to result.getObject(it + 1)
}
}
data
} ?: emptyList()
println(plan)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package org.example.examples

import org.jetbrains.exposed.sql.BooleanColumnType
import org.jetbrains.exposed.sql.DoubleColumnType
import org.jetbrains.exposed.sql.StdOutSqlLogger
import org.jetbrains.exposed.sql.VarCharColumnType
import org.jetbrains.exposed.sql.addLogger
import org.jetbrains.exposed.sql.statements.StatementType
import org.jetbrains.exposed.sql.transactions.transaction

/*
Important: This file is referenced by line number in `Working-with-SQL-Strings.md`.
If you add, remove, or modify any lines, ensure you update the corresponding
line numbers in the `code-block` element of the referenced file.
*/

private const val MAX_TITLE_LENGTH = 150
private const val NEW_TITLE = "The New Film"
private const val NEW_RATING = 9.0

class ExecMySQLExamples {
fun execMultipleStrings() {
transaction {
addLogger(StdOutSqlLogger)

val insertStmt = "INSERT INTO Films (title, rating, nominated) VALUES (?, ?, ?)"
val lastIdAlias = "last_id"
val selectStmt = "SELECT LAST_INSERT_ID() AS $lastIdAlias"
val lastId = exec(
stmt = "$insertStmt; $selectStmt;",
args = listOf(
VarCharColumnType(MAX_TITLE_LENGTH) to NEW_TITLE,
DoubleColumnType() to NEW_RATING,
BooleanColumnType() to false
),
explicitStatementType = StatementType.MULTI
) { result ->
result.next()
result.getInt(lastIdAlias)
}
println(lastId)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.example.tables

import org.jetbrains.exposed.sql.Table

const val MAX_TITLE_LENGTH = 150

object FilmsTable : Table() {
val id = integer("id").autoIncrement()
val title = varchar("title", MAX_TITLE_LENGTH)
val rating = double("rating")
val nominated = bool("nominated")

override val primaryKey = PrimaryKey(id)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
guava = "33.0.0-jre"
junit-jupiter-engine = "5.10.2"
exposed = "0.58.0"
h2 = "2.2.224"
slf4j = "2.0.16"

[libraries]
guava = { module = "com.google.guava:guava", version.ref = "guava" }
Expand All @@ -13,6 +15,8 @@ exposed-core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "e
exposed-jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
exposed-dao = { module = "org.jetbrains.exposed:exposed-dao", version.ref = "exposed" }
exposed-kotlin-datetime = { module = "org.jetbrains.exposed:exposed-kotlin-datetime", version.ref = "exposed" }
h2 = { module = "com.h2database:h2", version.ref = "h2"}
slf4j = {module = "org.slf4j:slf4j-nop", version.ref = "slf4j"}

[plugins]
jvm = { id = "org.jetbrains.kotlin.jvm", version = "1.9.22" }
jvm = { id = "org.jetbrains.kotlin.jvm", version = "2.1.0" }
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ include("exposed-dsl")
include("exposed-modules-maven")
include("exposed-modules-kotlin-gradle")
include("exposed-modules-groovy-gradle")
include("exposed-transactions")
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@
</p>
<tip>
For more details about using statement interceptors, see
<a href="Transactions.md" anchor="statement-interceptors">DSL Statement Interceptors</a>.
<a href="Statement-Interceptors.md">DSL Statement Interceptors</a>.
</tip>
</chapter>

Expand Down
Loading

0 comments on commit ebf2670

Please sign in to comment.