PostgreSQL Dialect
The PostgreSQL dialect extends the base SQL dialect with double-quoted column identifiers. It inherits all SQL predicates, conjunctions, transforms, and clauses — the only thing that differs from the base SQL dialect is how column names are rendered.
Dependency
libraryDependencies += "com.eff3ct" %% "criteria4s-postgresql" % "1.0.0"
Import Pattern
import com.eff3ct.criteria4s.core.*
import com.eff3ct.criteria4s.dialect.postgresql.{*, given}
import com.eff3ct.criteria4s.functions as F
import com.eff3ct.criteria4s.extensions.*
Column Quoting
PostgreSQL uses double-quoted identifiers, which allows reserved words and special characters in column names. This means every column reference in an expression will be wrapped in double quotes:
val column = summon[Show[Column, PostgreSQL]]
// column: Show[Column, PostgreSQL] = com.eff3ct.criteria4s.core.Show$$$Lambda$2369/0x00007ff230779010@62c1ad64
column.show(Column("user_name"))
// res0: String = "\"user_name\""
F.===[PostgreSQL, Column, Int](F.col("age"), F.lit(30)).value
// res1: String = "\"age\" = 30"
Inherited Operations
PostgreSQL inherits every operation from the base SQL dialect. All predicates, conjunctions, transforms, ordering, LIMIT/OFFSET, and CASE WHEN work identically — only the column quoting differs.
Predicate Examples
// Comparison
F.gt[PostgreSQL, Column, Int](F.col("age"), F.lit(18)).value
// res2: String = "\"age\" > 18"
F.leq[PostgreSQL, Column, Int](F.col("score"), F.lit(100)).value
// res3: String = "\"score\" <= 100"
// Pattern matching
F.like[PostgreSQL, Column, String](F.col("email"), F.lit("%@gmail.com")).value
// res4: String = "\"email\" LIKE %@gmail.com"
// Set membership
F.in[PostgreSQL, Column, Seq[String]](
F.col("status"), F.array[PostgreSQL, String]("active", "pending")
).value
// res5: String = "\"status\" IN (active, pending)"
// Null checks
F.isNull[PostgreSQL, Column](F.col("deleted_at")).value
// res6: String = "\"deleted_at\" IS NULL"
F.isNotNull[PostgreSQL, Column](F.col("email")).value
// res7: String = "\"email\" IS NOT NULL"
// Range
F.between[PostgreSQL, Column, (Int, Int)](
F.col("age"), F.range[PostgreSQL, Int](18, 65)
).value
// res8: String = "\"age\" BETWEEN 18 AND 65"
// Boolean
F.isTrue[PostgreSQL, Column](F.col("verified")).value
// res9: String = "\"verified\" IS TRUE"
Practical Examples
User search with pagination-ready WHERE clause
val userSearch = F.col[PostgreSQL]("age")
.geq(F.lit[PostgreSQL, Int](21))
.and(F.col[PostgreSQL]("country") === F.lit[PostgreSQL, String]("US"))
.and(F.col[PostgreSQL]("deleted_at").isNull)
// userSearch: Criteria[PostgreSQL] = (("age" >= 21) AND ("country" = US)) AND ("deleted_at" IS NULL)
userSearch.value
// res10: String = "((\"age\" >= 21) AND (\"country\" = US)) AND (\"deleted_at\" IS NULL)"
Case-insensitive search using UPPER
val ciSearch = F.upper[PostgreSQL, Column](F.col("email"))
.===(F.lit[PostgreSQL, String]("ADMIN@EXAMPLE.COM"))
// ciSearch: Criteria[PostgreSQL] = UPPER("email") = ADMIN@EXAMPLE.COM
ciSearch.value
// res11: String = "UPPER(\"email\") = ADMIN@EXAMPLE.COM"
Active users or recent signups
val filter = F.col[PostgreSQL]("active")
.isTrue
.or(F.col[PostgreSQL]("created_at") :> F.lit[PostgreSQL, String]("2025-01-01"))
// filter: Criteria[PostgreSQL] = ("active" IS TRUE) OR ("created_at" > 2025-01-01)
filter.value
// res12: String = "(\"active\" IS TRUE) OR (\"created_at\" > 2025-01-01)"