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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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.SalesTable
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(SalesTable)
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.SalesTable
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 minAmount = SalesTable.amount.min()
val maxAmount = SalesTable.amount.max()
val averageAmount = SalesTable.amount.avg()
val amountStats = SalesTable
.select(minAmount, maxAmount, averageAmount, SalesTable.label)
.groupBy(SalesTable.label)
.map {
Triple(it[minAmount], it[maxAmount], it[averageAmount])
}
println(amountStats)

val amountSum = SalesTable.amount.sum()
val amountCount = SalesTable.amount.count()
val amountReport = SalesTable
.select(amountSum, amountCount, SalesTable.label)
.groupBy(SalesTable.label)
.map {
it[amountSum] to it[amountCount]
}
println(amountReport)

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

import org.example.tables.SalesTable
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 SALES_MONTH = 1
private const val SALES_YEAR = 2025

class CustomFuncExamples {
fun selectCustomFunctions() {
val sqrtAmount = SalesTable.amount.function("SQRT")
// generates SQL: SQRT(SALES.AMOUNT)
val sqrt = SalesTable
.select(sqrtAmount)
.singleOrNull()
?.get(sqrtAmount)
println(sqrt)

val replacedLabel = CustomFunction(
functionName = "REPLACE",
columnType = TextColumnType(),
SalesTable.label, stringLiteral("Label"), stringLiteral("New Label")
)
// generates SQL: REPLACE(SALES.LABEL, 'Label', 'New Label')
val replacedLabels = SalesTable.select(replacedLabel).map { it[replacedLabel] }
println(replacedLabels)

val replacedStringLabel = CustomStringFunction(
"REPLACE", SalesTable.label, stringLiteral("Label"), stringLiteral("New Label")
)
val replacedStringLabels = SalesTable.select(replacedStringLabel).map { it[replacedStringLabel] }
println(replacedStringLabels)
}

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

fun selectCustomTrimFunction() {
SalesTable.deleteAll()

SalesTable.insert {
it[label] = "xxxxLabelxxxx"
it[product] = "Product"
it[amount] = 99.toBigDecimal()
it[month] = SALES_MONTH
it[year] = SALES_YEAR
}

val leadingXTrim = SalesTable.label.customTrim(stringLiteral("x"), TrimSpecifier.LEADING)
val labelWithoutPrefix = SalesTable.select(leadingXTrim).single()[leadingXTrim] // Labelxxxx
println(labelWithoutPrefix)

val trailingXTrim = SalesTable.label.customTrim(stringLiteral("x"), TrimSpecifier.TRAILING)
val labelWithoutSuffix = SalesTable.select(trailingXTrim).single()[trailingXTrim] // xxxxLabel
println(labelWithoutSuffix)
}
}
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.SalesTable
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 SALES_MONTH = 1
private const val SALES_YEAR = 2025

class StringFuncExamples {
fun selectStringFunctions() {
SalesTable.insert {
it[label] = "Label A"
it[product] = "Product A"
it[amount] = 99.toBigDecimal()
it[month] = SALES_MONTH
it[year] = SALES_YEAR
}

val lowerCaseLabel = SalesTable.label.lowerCase()
val lowerCaseLabels = SalesTable.select(lowerCaseLabel).map { it[lowerCaseLabel] }
println(lowerCaseLabels)

val upperCaseProduct = SalesTable.product.upperCase().alias("prd_all_caps")
val upperCaseProducts = SalesTable.select(upperCaseProduct).map { it[upperCaseProduct] }
println(upperCaseProducts)

val fullProductLabel = Concat(separator = " ", SalesTable.product, stringLiteral("||"), SalesTable.label)
.trim()
.lowerCase()
val fullProductLabels = SalesTable.select(fullProductLabel).map { it[fullProductLabel] }
println(fullProductLabels)

val shortenedLabel = SalesTable.label.substring(start = 1, length = 3)
val shortenedLabels = SalesTable.select(shortenedLabel).map { it[shortenedLabel] }
println(shortenedLabels)

val productName = concat(
separator = " - ",
expr = listOf(stringLiteral("Product"), SalesTable.product)
)
val productNames = SalesTable.select(productName).map { it[productName] }
println(productNames)

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

val labelLength = SalesTable.label.charLength()
val labelLengths = SalesTable.select(labelLength).map { it[labelLength] }
println(labelLengths)
}
}
Loading
Loading