Name Transforms
Automatic camelCase/snake_case conversion for query identifiers and result columns
Name Transforms
Overview
@effect/sql-pg supports bidirectional name transformation between TypeScript camelCase and PostgreSQL snake_case. This is configured via transformQueryNames and transformResultNames in PgClientConfig.
Key Concepts
Two-Way Transforms
transformQueryNames: Applied to SQL identifiers generated by helpers (insert,update,updateValues, identifier escaping). Write camelCase in TS, it becomes snake_case in SQL.transformResultNames: Applied to column names in query results. DB snake_case comes back as camelCase in TS.
PgClient.layer({
database: "mydb",
transformQueryNames: String.camelToSnake,
transformResultNames: String.snakeToCamel
})
Effect on Helpers
With transformQueryNames: String.camelToSnake:
sql`INSERT INTO people ${sql.insert({ firstName: "Tim", age: 10 })}`
// → INSERT INTO people ("first_name","age") VALUES ($1,$2)
sql`SELECT * FROM ${sql("peopleTest")}`
// → SELECT * FROM "people_test"
JSON Transform Behavior
The transformJson option (default true) controls whether transforms recurse into JSON/array values. With Statement.defaultTransforms:
Nested mode (transformJson: true, the default):
// Input from DB: { a_key: 1, nested: [{ b_key: 2 }], arr_primitive: [1, "2", true] }
// Output: { aKey: 1, nested: [{ bKey: 2 }], arrPrimitive: [1, "2", true] }
Non-nested mode (transformJson: false):
// Input from DB: { a_key: 1, nested: [{ b_key: 2 }] }
// Output: { aKey: 1, nested: [{ b_key: 2 }] } ← nested objects NOT transformed
withoutTransforms()
To bypass transforms for specific queries:
const raw = (yield* PgClient.PgClient).withoutTransforms()
raw`INSERT INTO people ${raw.insert({ first_name: "Tim" })}`
// Column names used as-is
Compiler Integration
The makeCompiler function accepts a transform and applies it via the onIdentifier hook:
const compiler = PgClient.makeCompiler(String.camelToSnake)
// onIdentifier: (value, withoutTransform) =>
// withoutTransform ? escape(value) : escape(transform(value))
The withoutTransform flag is used internally when the compiler knows the identifier is already in the correct case.
Code Examples
From the test suite:
// With transforms: camelCase TS → snake_case SQL
const sql = yield* PgClient.PgClient
sql`INSERT INTO people ${sql.insert({ firstName: "Tim" })}`
// → ("first_name") VALUES ($1)
// Without transforms
const raw = sql.withoutTransforms()
raw`INSERT INTO people ${raw.insert({ first_name: "Tim" })}`
// → ("first_name") VALUES ($1)
Related Files
src/PgClient.ts-makeCompiler,ClientOptions, transform wiring inmakeClienttest/Client.test.ts- Transform tests (nested, non-nested, withoutTransforms)