Skip to main content

Transforms

Transforms wrap column references (or literal values) in SQL functions before they are used in predicates. A transform takes a Ref[T, V] and returns a new Ref[T, V], so you can compose them freely with any predicate.

import com.eff3ct.criteria4s.core.*
import com.eff3ct.criteria4s.dialect.sql.{given, *}
import com.eff3ct.criteria4s.extensions.*
import com.eff3ct.criteria4s.functions as F

Unary Transforms

These transforms wrap a single reference.

UPPER

Converts a column or value to uppercase. You can use either the function or extension style:

// Function-style
val upperFunc = F.upper[SQL, Column](F.col("name"))
// upperFunc: Ref[SQL, Column] = com.eff3ct.criteria4s.core.Ref$$anon$8@1e24d794

// Extension-style
val upperExt = F.col[SQL]("name").upper
// upperExt: Ref[SQL, Column] = com.eff3ct.criteria4s.core.Ref$$anon$8@2a6cf628

LOWER

Converts a column or value to lowercase:

// Function-style
val lowerFunc = F.lower[SQL, Column](F.col("name"))
// lowerFunc: Ref[SQL, Column] = com.eff3ct.criteria4s.core.Ref$$anon$8@5297b2e7

// Extension-style
val lowerExt = F.col[SQL]("name").lower
// lowerExt: Ref[SQL, Column] = com.eff3ct.criteria4s.core.Ref$$anon$8@7699d984

TRIM

Removes leading and trailing whitespace from a column or value:

// Function-style
val trimFunc = F.trim[SQL, Column](F.col("name"))
// trimFunc: Ref[SQL, Column] = com.eff3ct.criteria4s.core.Ref$$anon$8@49933cf7

// Extension-style
val trimExt = F.col[SQL]("name").trim
// trimExt: Ref[SQL, Column] = com.eff3ct.criteria4s.core.Ref$$anon$8@72ad156

Binary Transforms

These transforms combine two references into a single Ref value.

COALESCE

Returns the first non-null value from two references. Useful for providing a fallback column when one might be null:

// Function-style
val coalesceFunc = F.coalesce[SQL, Column](F.col("nickname"), F.col("name"))
// coalesceFunc: Ref[SQL, Column] = com.eff3ct.criteria4s.core.Ref$$anon$9@14a34d9f

Once you have a coalesced reference, you use it in a predicate just like any other Ref:

val coalesceExpr = F.coalesce[SQL, Column](F.col("nickname"), F.col("name")) === F.lit[SQL, String]("John")
// coalesceExpr: Criteria[SQL] = COALESCE(nickname, name) = 'John'
coalesceExpr.value
// res0: String = "COALESCE(nickname, name) = 'John'"

Renders to SQL: COALESCE(nickname, name) = 'John'

CONCAT

Concatenates two values together:

// Function-style -- concatenating two literals
val concatLiterals = F.concat[SQL, String](F.lit("Hello"), F.lit(" World"))
// concatLiterals: Ref[SQL, String] = com.eff3ct.criteria4s.core.Ref$$anon$9@365373b2

// Function-style -- concatenating two columns
val concatCols = F.concat[SQL, Column](F.col("first_name"), F.col("last_name"))
// concatCols: Ref[SQL, Column] = com.eff3ct.criteria4s.core.Ref$$anon$9@6964c466

Composing Transforms with Predicates

The real power of transforms is that they return Ref values, so they plug directly into any predicate. This lets you write case-insensitive comparisons, trimmed matches, and more without any special syntax.

Case-insensitive equality

val caseInsensitive = F.upper[SQL, Column](F.col("name")) === F.lit[SQL, String]("JOHN")
// caseInsensitive: Criteria[SQL] = UPPER(name) = 'JOHN'
caseInsensitive.value
// res1: String = "UPPER(name) = 'JOHN'"

Renders to SQL: UPPER(name) = 'JOHN'

Trimmed comparison

val trimmedMatch = F.trim[SQL, Column](F.col("code")) === F.lit[SQL, String]("ABC")
// trimmedMatch: Criteria[SQL] = TRIM(code) = 'ABC'
trimmedMatch.value
// res2: String = "TRIM(code) = 'ABC'"

Renders to SQL: TRIM(code) = 'ABC'

val lowerLike = F.lower[SQL, Column](F.col("email")) like F.lit[SQL, String]("%@example.com")
// lowerLike: Criteria[SQL] = LOWER(email) LIKE '%@example.com'
lowerLike.value
// res3: String = "LOWER(email) LIKE '%@example.com'"

Renders to SQL: LOWER(email) LIKE '%@example.com'

Extension syntax composition

With extension-style, transforms chain naturally before predicates, so the code reads almost like plain prose:

val extComposed = F.col[SQL]("name").upper === F.lit[SQL, String]("ALICE")
// extComposed: Criteria[SQL] = UPPER(name) = 'ALICE'
extComposed.value
// res4: String = "UPPER(name) = 'ALICE'"

val extLower = F.col[SQL]("email").lower like F.lit[SQL, String]("%@company.com")
// extLower: Criteria[SQL] = LOWER(email) LIKE '%@company.com'
extLower.value
// res5: String = "LOWER(email) LIKE '%@company.com'"

val extTrim = F.col[SQL]("code").trim === F.lit[SQL, String]("XYZ")
// extTrim: Criteria[SQL] = TRIM(code) = 'XYZ'
extTrim.value
// res6: String = "TRIM(code) = 'XYZ'"

Combining transforms in a filter

You can mix transforms with regular predicates in a single compound expression:

val searchFilter =
(F.col[SQL]("name").upper === F.lit[SQL, String]("JOHN"))
.and(F.col[SQL]("email").lower like F.lit[SQL, String]("%@example.com"))
.and(F.col[SQL]("deleted_at").isNull)
// searchFilter: Criteria[SQL] = ((UPPER(name) = 'JOHN') AND (LOWER(email) LIKE '%@example.com')) AND (deleted_at IS NULL)

searchFilter.value
// res7: String = "((UPPER(name) = 'JOHN') AND (LOWER(email) LIKE '%@example.com')) AND (deleted_at IS NULL)"

Quick Reference

TransformFunction-styleExtension-styleSQL output
UPPERF.upper(ref)ref.upperUPPER(col)
LOWERF.lower(ref)ref.lowerLOWER(col)
TRIMF.trim(ref)ref.trimTRIM(col)
COALESCEF.coalesce(ref1, ref2)(no extension alias)COALESCE(a, b)
CONCATF.concat(ref1, ref2)(no extension alias)CONCAT(a, b)