Skip to content
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ 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.

- **EXPERIMENTAL**: Adds end-to-end support for window functions including RANK, DENSE_RANK, LAG, LEAD, and ROW_NUMBER functions alongside the WINDOW clause.

### Changed
- Formalize `CONCAT` length handling on string types

### Deprecated
- Deprecated previous modeling of window functions.
Expand Down
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 LowerTest {

@Test
fun `lower preserves CHAR length`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("LOWER(CAST('hello' AS CHAR(5)))")
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(5, actualType.length)
}

@Test
fun `lower preserves VARCHAR length`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("LOWER(CAST('hello' AS VARCHAR(10)))")
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(10, actualType.length)
}

@Test
fun `lower preserves CLOB type`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("LOWER(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 `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
@@ -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 UpperTest {

@Test
fun `upper preserves CHAR length`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("UPPER(CAST('HELLO' AS CHAR(5)))")
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(5, actualType.length)
}

@Test
fun `upper preserves VARCHAR length`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("UPPER(CAST('HELLO' AS VARCHAR(10)))")
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(10, actualType.length)
}

@Test
fun `upper preserves CLOB type`() {
val session = Session.builder()
.catalog("default")
.catalogs(
TestCatalog.builder()
.name("default")
.build()
)
.build()
val parseResult = PartiQLParser.standard().parse("UPPER(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 `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 @@ -147,6 +147,8 @@ internal object Builtins {
Fn_LIKE_ESCAPE__STRING_STRING_STRING__BOOL,
Fn_LIKE_ESCAPE__CLOB_CLOB_CLOB__BOOL,

Fn_LOWER__CHAR__CHAR,
Fn_LOWER__VARCHAR__VARCHAR,
Fn_LOWER__STRING__STRING,
Fn_LOWER__CLOB__CLOB,

Expand Down Expand Up @@ -184,6 +186,8 @@ internal object Builtins {
Fn_SUBSTRING__CLOB_INT64_INT64__CLOB,

FnTimes,
Fn_TRIM__CHAR__CHAR,
Fn_TRIM__VARCHAR__VARCHAR,
Fn_TRIM__STRING__STRING,
Fn_TRIM__CLOB__CLOB,

Expand All @@ -202,6 +206,8 @@ internal object Builtins {
Fn_TRIM_TRAILING_CHARS__STRING_STRING__STRING,
Fn_TRIM_TRAILING_CHARS__CLOB_CLOB__CLOB,

Fn_UPPER__CHAR__CHAR,
Fn_UPPER__VARCHAR__VARCHAR,
Fn_UPPER__STRING__STRING,
Fn_UPPER__CLOB__CLOB,

Expand Down
Loading
Loading