grimoire

grimoire show @effect/sql-pg name-transforms

Name Transforms

Automatic camelCase/snake_case conversion for query identifiers and result columns

transformscamelCasesnake_casejson

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

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