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: EXPOSED-715 Fix broken custom function examples & move to example project #2383

Merged
merged 4 commits into from
Feb 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Exposed SQL Functions examples

A Gradle application that shows how to work with built-in and custom SQL functions using Exposed API.
The files are referenced in the Schema's [SQL Functions](../../topics/SQL-Functions.md) topic.

## 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.exposed.kotlin.datetime)
implementation(libs.h2)
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.AggregateFuncExamples
import org.example.examples.CustomFuncExamples
import org.example.examples.StringFuncExamples
import org.example.examples.WindowFuncExamples
import org.example.tables.FilmBoxOfficeTable
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",
"org.h2.Driver",
databaseConfig = DatabaseConfig { useNestedTransactions = true }
)

transaction {
addLogger(StdOutSqlLogger)
SchemaUtils.create(FilmBoxOfficeTable)
runStringFuncExamples()
runAggregateFuncExamples()
runWindowFuncExamples()
runCustomFuncExamples()
}
}

fun runStringFuncExamples() {
val stringFuncExamples = StringFuncExamples()
stringFuncExamples.selectStringFunctions()
}

fun runAggregateFuncExamples() {
val aggregateFuncExamples = AggregateFuncExamples()
aggregateFuncExamples.selectAggregateFunctions()
}

fun runWindowFuncExamples() {
val windowFuncExamples = WindowFuncExamples()
windowFuncExamples.selectWindowFunctions()
}

fun runCustomFuncExamples() {
val customFuncExamples = CustomFuncExamples()
customFuncExamples.selectCustomFunctions()
customFuncExamples.selectCustomTrimFunction()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.example.examples

import org.example.tables.FilmBoxOfficeTable
import org.jetbrains.exposed.sql.avg
import org.jetbrains.exposed.sql.count
import org.jetbrains.exposed.sql.max
import org.jetbrains.exposed.sql.min
import org.jetbrains.exposed.sql.stdDevPop
import org.jetbrains.exposed.sql.sum

/*
Important: This file is referenced by line number in `SQL-Functions.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.
*/

class AggregateFuncExamples {
fun selectAggregateFunctions() {
val minRevenue = FilmBoxOfficeTable.revenue.min()
val maxRevenue = FilmBoxOfficeTable.revenue.max()
val averageRevenue = FilmBoxOfficeTable.revenue.avg()
val revenueStats = FilmBoxOfficeTable
.select(minRevenue, maxRevenue, averageRevenue, FilmBoxOfficeTable.region)
.groupBy(FilmBoxOfficeTable.region)
.map {
Triple(it[minRevenue], it[maxRevenue], it[averageRevenue])
}
println(revenueStats)

val revenueSum = FilmBoxOfficeTable.revenue.sum()
val revenueCount = FilmBoxOfficeTable.revenue.count()
val revenueReport = FilmBoxOfficeTable
.select(revenueSum, revenueCount, FilmBoxOfficeTable.region)
.groupBy(FilmBoxOfficeTable.region)
.map {
it[revenueSum] to it[revenueCount]
}
println(revenueReport)

val revenueStdDev = FilmBoxOfficeTable.revenue.stdDevPop()
val stdDev = FilmBoxOfficeTable
.select(revenueStdDev)
.singleOrNull()
?.get(revenueStdDev)
println(stdDev)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package org.example.examples

import org.example.tables.FilmBoxOfficeTable
import org.jetbrains.exposed.sql.CustomFunction
import org.jetbrains.exposed.sql.CustomStringFunction
import org.jetbrains.exposed.sql.TextColumnType
import org.jetbrains.exposed.sql.deleteAll
import org.jetbrains.exposed.sql.function
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.intLiteral
import org.jetbrains.exposed.sql.kotlin.datetime.CurrentDate
import org.jetbrains.exposed.sql.kotlin.datetime.CustomDateFunction
import org.jetbrains.exposed.sql.kotlin.datetime.month
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.stringLiteral

/*
Important: This file is referenced by line number in `SQL-Functions.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 REVENUE_MONTH = 1
private const val REVENUE_YEAR = 1999

class CustomFuncExamples {
fun selectCustomFunctions() {
val sqrtRevenue = FilmBoxOfficeTable.revenue.function("SQRT")
// generates SQL: SQRT(SALES.REVENUE)
val sqrt = FilmBoxOfficeTable
.select(sqrtRevenue)
.singleOrNull()
?.get(sqrtRevenue)
println(sqrt)

val replacedTitle = CustomFunction(
functionName = "REPLACE",
columnType = TextColumnType(),
FilmBoxOfficeTable.title, stringLiteral("Title"), stringLiteral("New Title")
)
// generates SQL: REPLACE(FILMBOXOFFICE.TITLE, 'Title', 'New Title')
val replacedTitles = FilmBoxOfficeTable.select(replacedTitle).map { it[replacedTitle] }
println(replacedTitles)

val replacedStringTitle = CustomStringFunction(
"REPLACE", FilmBoxOfficeTable.title, stringLiteral("Title"), stringLiteral("New Title")
)
val replacedStringTitles = FilmBoxOfficeTable.select(replacedStringTitle).map { it[replacedStringTitle] }
println(replacedStringTitles)
}

@Suppress("MagicNumber")
fun selectCustomDateFunction() {
val threeMonthsAgo = CustomDateFunction(
functionName = "DATEADD",
stringLiteral("MONTH"),
intLiteral(-3),
CurrentDate
).month()
// generates SQL: MONTH(DATEADD('MONTH', -3, CURRENT_DATE))
val filmsInLast3Months = FilmBoxOfficeTable
.selectAll()
.where { FilmBoxOfficeTable.month greater threeMonthsAgo }
.map { it[FilmBoxOfficeTable.title] }
println(filmsInLast3Months)
}

fun selectCustomTrimFunction() {
FilmBoxOfficeTable.deleteAll()

FilmBoxOfficeTable.insert {
it[title] = "Star Wars: The Phantom Menace - Episode I"
it[region] = "Spain"
it[revenue] = 99.toBigDecimal()
it[month] = REVENUE_MONTH
it[year] = REVENUE_YEAR
}

val leadingStarWarsTrim = FilmBoxOfficeTable.title.customTrim(stringLiteral("Star Wars:"), TrimSpecifier.LEADING)
val titleWithoutPrefix = FilmBoxOfficeTable.select(leadingStarWarsTrim).single()[leadingStarWarsTrim] // The Phantom Menace - Episode I
println(titleWithoutPrefix)

val trailingEpisodeTrim = FilmBoxOfficeTable.title.customTrim(stringLiteral("- Episode I"), TrimSpecifier.TRAILING)
val titleWithoutSuffix = FilmBoxOfficeTable.select(trailingEpisodeTrim).single()[trailingEpisodeTrim] // Star Wars: The Phantom Menace
println(titleWithoutSuffix)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.example.examples

import org.jetbrains.exposed.sql.Expression
import org.jetbrains.exposed.sql.Function
import org.jetbrains.exposed.sql.QueryBuilder
import org.jetbrains.exposed.sql.TextColumnType
import org.jetbrains.exposed.sql.append

enum class TrimSpecifier { BOTH, LEADING, TRAILING }

class CustomTrim<T : String?>(
val expression: Expression<T>,
val toRemove: Expression<T>?,
val trimSpecifier: TrimSpecifier
) : Function<String>(TextColumnType()) {
override fun toQueryBuilder(queryBuilder: QueryBuilder) {
queryBuilder {
append("TRIM(")
append(trimSpecifier.name)
toRemove?.let { +" $it" }
append(" FROM ", expression, ")")
}
}
}

fun <T : String?> Expression<T>.customTrim(
toRemove: Expression<T>? = null,
specifier: TrimSpecifier = TrimSpecifier.BOTH
): CustomTrim<T> = CustomTrim(this, toRemove, specifier)
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package org.example.examples

import org.example.tables.FilmBoxOfficeTable
import org.jetbrains.exposed.sql.Concat
import org.jetbrains.exposed.sql.SqlExpressionBuilder.concat
import org.jetbrains.exposed.sql.alias
import org.jetbrains.exposed.sql.charLength
import org.jetbrains.exposed.sql.insert
import org.jetbrains.exposed.sql.locate
import org.jetbrains.exposed.sql.lowerCase
import org.jetbrains.exposed.sql.stringLiteral
import org.jetbrains.exposed.sql.substring
import org.jetbrains.exposed.sql.trim
import org.jetbrains.exposed.sql.upperCase

/*
Important: This file is referenced by line number in `SQL-Functions.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 REVENUE_MONTH = 1
private const val REVENUE_YEAR = 2019

class StringFuncExamples {
fun selectStringFunctions() {
FilmBoxOfficeTable.insert {
it[title] = "The Rise of Skywalker"
it[region] = "Netherlands"
it[revenue] = 99.toBigDecimal()
it[month] = REVENUE_MONTH
it[year] = REVENUE_YEAR
}

val lowerCaseTitle = FilmBoxOfficeTable.title.lowerCase()
val lowerCaseTitles = FilmBoxOfficeTable.select(lowerCaseTitle).map { it[lowerCaseTitle] }
println(lowerCaseTitles)

val upperCaseRegion = FilmBoxOfficeTable.region.upperCase().alias("reg_all_caps")
val upperCaseRegions = FilmBoxOfficeTable.select(upperCaseRegion).map { it[upperCaseRegion] }
println(upperCaseRegions)

val fullFilmTitle = Concat(separator = " ", FilmBoxOfficeTable.region, stringLiteral("||"), FilmBoxOfficeTable.title)
.trim()
.lowerCase()
val fullFilmTitles = FilmBoxOfficeTable.select(fullFilmTitle).map { it[fullFilmTitle] }
println(fullFilmTitles)

val shortenedTitle = FilmBoxOfficeTable.title.substring(start = 1, length = 3)
val shortenedTitles = FilmBoxOfficeTable.select(shortenedTitle).map { it[shortenedTitle] }
println(shortenedTitles)

val filmTitle = concat(
separator = " - ",
expr = listOf(stringLiteral("Title"), FilmBoxOfficeTable.title)
)
val filmTitles = FilmBoxOfficeTable.select(filmTitle).map { it[filmTitle] }
println(filmTitles)

val firstXSIndex = FilmBoxOfficeTable.title.locate("XS")
val firstXSIndices = FilmBoxOfficeTable.select(firstXSIndex).map { it[firstXSIndex] }
println(firstXSIndices)

val titleLength = FilmBoxOfficeTable.title.charLength()
val titleLengths = FilmBoxOfficeTable.select(titleLength).map { it[titleLength] }
println(titleLengths)
}
}
Loading
Loading