Skip to content
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Thank you to all who have contributed!
## [Unreleased](https://TODO.com) - YYYY-MM-DD

### Added
- `UPPER`, `LOWER`, `TRIM` function support for `CHAR/VARCHAR` types
Copy link
Contributor Author

Choose a reason for hiding this comment

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

self-review: Since the length handling should also be done at planning time, add the real function support instead of only siganatures.


### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,23 @@ class LowerTest {
val actualType = query.rex.type.pType
assertEquals(PType.CLOB, actualType.code())
}

@Test
fun `lower preserves STRING type`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("LOWER('hello')")
val ast = parseResult.statements[0]
val planner = PartiQLPlanner.builder().build()
val result = planner.plan(ast, session)
val query = result.plan.action as Action.Query
val actualType = query.rex.type.pType
assertEquals(PType.STRING, actualType.code())
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package org.partiql.planner.internal.typer.functions

import org.junit.jupiter.api.Test
import org.partiql.parser.PartiQLParser
import org.partiql.plan.Action
import org.partiql.planner.PartiQLPlanner
import org.partiql.planner.internal.TestCatalog
import org.partiql.spi.catalog.Session
import org.partiql.spi.types.PType
import kotlin.test.assertEquals

class TrimTest {

@Test
fun `trim preserves CHAR length`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("TRIM(CAST(' HELLO ' AS CHAR(9)))")
val ast = parseResult.statements[0]
val planner = PartiQLPlanner.builder().build()
val result = planner.plan(ast, session)
val query = result.plan.action as Action.Query
val actualType = query.rex.type.pType
assertEquals(PType.CHAR, actualType.code())
assertEquals(9, actualType.length)
}

@Test
fun `trim preserves VARCHAR length`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("TRIM(CAST(' HELLO ' AS VARCHAR(15)))")
val ast = parseResult.statements[0]
val planner = PartiQLPlanner.builder().build()
val result = planner.plan(ast, session)
val query = result.plan.action as Action.Query
val actualType = query.rex.type.pType
assertEquals(PType.VARCHAR, actualType.code())
assertEquals(15, actualType.length)
}

@Test
fun `trim preserves CLOB type`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("TRIM(CAST(' HELLO ' AS CLOB))")
val ast = parseResult.statements[0]
val planner = PartiQLPlanner.builder().build()
val result = planner.plan(ast, session)
val query = result.plan.action as Action.Query
val actualType = query.rex.type.pType
assertEquals(PType.CLOB, actualType.code())
}

@Test
fun `trim preserves STRING type`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("TRIM(' HELLO ')")
val ast = parseResult.statements[0]
val planner = PartiQLPlanner.builder().build()
val result = planner.plan(ast, session)
val query = result.plan.action as Action.Query
val actualType = query.rex.type.pType
assertEquals(PType.STRING, actualType.code())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,23 @@ class UpperTest {
val actualType = query.rex.type.pType
assertEquals(PType.CLOB, actualType.code())
}

@Test
fun `upper preserves STRING type`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("UPPER('HELLO')")
val ast = parseResult.statements[0]
val planner = PartiQLPlanner.builder().build()
val result = planner.plan(ast, session)
val query = result.plan.action as Action.Query
val actualType = query.rex.type.pType
assertEquals(PType.STRING, actualType.code())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

package org.partiql.spi.function.builtins

import org.partiql.spi.function.Fn
import org.partiql.spi.function.FnOverload
import org.partiql.spi.function.Function
import org.partiql.spi.function.Parameter
import org.partiql.spi.function.RoutineOverloadSignature
import org.partiql.spi.types.PType
import org.partiql.spi.utils.FunctionUtils
import org.partiql.spi.utils.StringUtils.codepointTrim
Expand Down Expand Up @@ -38,42 +42,57 @@ import org.partiql.spi.value.Datum
* * `<trim character> ::= <character value expression>`
* * `<trim source> ::= <character value expression>`
*/
internal val Fn_TRIM__CHAR__CHAR = FunctionUtils.hidden(
name = "trim",
returns = PType.character(),
parameters = arrayOf(Parameter("value", PType.character())),
) { args ->
val string = args[0].bytes.toString(Charsets.UTF_8)
val result = string.codepointTrim()
Datum.character(result)
}
internal object FnTrim : FnOverload() {

internal val Fn_TRIM__VARCHAR__VARCHAR = FunctionUtils.hidden(
name = "trim",
returns = PType.varchar(),
parameters = arrayOf(Parameter("value", PType.varchar())),
) { args ->
val string = args[0].bytes.toString(Charsets.UTF_8)
val result = string.codepointTrim()
Datum.varchar(result)
}
override fun getSignature(): RoutineOverloadSignature {
return RoutineOverloadSignature(FunctionUtils.hide("trim"), listOf(PType.dynamic()))
}

internal val Fn_TRIM__STRING__STRING = FunctionUtils.hidden(
name = "trim",
returns = PType.string(),
parameters = arrayOf(Parameter("value", PType.string())),
) { args ->
val value = args[0].string
val result = value.codepointTrim()
Datum.string(result)
override fun getInstance(args: Array<PType>): Fn? {
val inputType = args[0]
return when (inputType.code()) {
PType.CHAR -> Function.instance(
name = FunctionUtils.hide("trim"),
returns = PType.character(inputType.length),
parameters = arrayOf(Parameter("value", inputType)),
) { args ->
val string = args[0].bytes.toString(Charsets.UTF_8)
val result = string.codepointTrim()
Datum.character(result, inputType.length)
}
PType.VARCHAR -> Function.instance(
name = FunctionUtils.hide("trim"),
returns = PType.varchar(inputType.length),
parameters = arrayOf(Parameter("value", inputType)),
) { args ->
val string = args[0].bytes.toString(Charsets.UTF_8)
val result = string.codepointTrim()
Datum.varchar(result, inputType.length)
}
PType.STRING -> Function.instance(
name = FunctionUtils.hide("trim"),
returns = PType.string(),
parameters = arrayOf(Parameter("value", inputType)),
) { args ->
val value = args[0].string
val result = value.codepointTrim()
Datum.string(result)
}
PType.CLOB -> Function.instance(
name = FunctionUtils.hide("trim"),
returns = PType.clob(inputType.length),
parameters = arrayOf(Parameter("value", inputType)),
) { args ->
val string = args[0].bytes.toString(Charsets.UTF_8)
val result = string.codepointTrim()
Datum.clob(result.toByteArray())
}
else -> null
}
}
}

internal val Fn_TRIM__CLOB__CLOB = FunctionUtils.hidden(
name = "trim",
returns = PType.clob(Int.MAX_VALUE),
parameters = arrayOf(Parameter("value", PType.clob(Int.MAX_VALUE))),
) { args ->
val string = args[0].bytes.toString(Charsets.UTF_8)
val result = string.codepointTrim()
Datum.clob(result.toByteArray())
}
internal val Fn_TRIM__CHAR__CHAR = FnTrim
internal val Fn_TRIM__VARCHAR__VARCHAR = FnTrim
internal val Fn_TRIM__STRING__STRING = FnTrim
internal val Fn_TRIM__CLOB__CLOB = FnTrim
Loading