effect-qb 0.15.0 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  import * as Query from "../../internal/query.js"
2
+ import type * as Expression from "../../internal/scalar.js"
2
3
  import { type RenderState } from "../../internal/dialect.js"
3
4
  import { postgresDialect } from "./dialect.js"
4
5
  import { type Projection } from "../../internal/projections.js"
@@ -14,16 +15,23 @@ export interface PostgresRenderResult {
14
15
  readonly sql: string
15
16
  readonly params: readonly unknown[]
16
17
  readonly projections: readonly Projection[]
18
+ readonly valueMappings?: Expression.DriverValueMappings
19
+ }
20
+
21
+ export interface PostgresRenderOptions {
22
+ readonly valueMappings?: Expression.DriverValueMappings
17
23
  }
18
24
 
19
25
  /**
20
26
  * Renders the current query AST into Postgres SQL plus bind parameters.
21
27
  */
22
28
  export const renderPostgresPlan = <PlanValue extends Query.Plan.Any>(
23
- plan: Query.DialectCompatiblePlan<PlanValue, "postgres">
29
+ plan: Query.DialectCompatiblePlan<PlanValue, "postgres">,
30
+ options: PostgresRenderOptions = {}
24
31
  ): PostgresRenderResult => {
25
32
  const state: RenderState = {
26
33
  params: [],
34
+ valueMappings: options.valueMappings,
27
35
  ctes: [],
28
36
  cteNames: new Set<string>()
29
37
  }
@@ -35,6 +43,7 @@ export const renderPostgresPlan = <PlanValue extends Query.Plan.Any>(
35
43
  return {
36
44
  sql: rendered.sql,
37
45
  params: state.params,
38
- projections: rendered.projections
46
+ projections: rendered.projections,
47
+ valueMappings: state.valueMappings
39
48
  }
40
49
  }
@@ -5,6 +5,11 @@ import * as QueryAst from "../../internal/query-ast.js"
5
5
  import type { RenderState, SqlDialect } from "../../internal/dialect.js"
6
6
  import * as ExpressionAst from "../../internal/expression-ast.js"
7
7
  import * as JsonPath from "../../internal/json/path.js"
8
+ import {
9
+ renderJsonSelectSql,
10
+ renderSelectSql,
11
+ toDriverValue
12
+ } from "../../internal/runtime/driver-value-mapping.js"
8
13
  import { flattenSelection, type Projection } from "../../internal/projections.js"
9
14
  import { type SelectionValue, validateAggregationSelection } from "../../internal/aggregation-validation.js"
10
15
  import * as SchemaExpression from "../../internal/schema-expression.js"
@@ -294,11 +299,54 @@ const renderPostgresJsonValue = (
294
299
  throw new Error("Expected a JSON expression")
295
300
  }
296
301
  const rendered = renderExpression(value, state, dialect)
302
+ const ast = (value as Expression.Any & {
303
+ readonly [ExpressionAst.TypeId]: ExpressionAst.Any
304
+ })[ExpressionAst.TypeId]
305
+ if (ast.kind === "literal") {
306
+ return `cast(${rendered} as jsonb)`
307
+ }
297
308
  return value[Expression.TypeId].dbType.kind === "jsonb"
298
309
  ? rendered
299
310
  : `cast(${rendered} as jsonb)`
300
311
  }
301
312
 
313
+ const expressionDriverContext = (
314
+ expression: Expression.Any,
315
+ state: RenderState,
316
+ dialect: SqlDialect
317
+ ) => ({
318
+ dialect: dialect.name,
319
+ valueMappings: state.valueMappings,
320
+ dbType: expression[Expression.TypeId].dbType,
321
+ runtimeSchema: expression[Expression.TypeId].runtimeSchema,
322
+ driverValueMapping: expression[Expression.TypeId].driverValueMapping
323
+ })
324
+
325
+ const renderJsonInputExpression = (
326
+ expression: Expression.Any,
327
+ state: RenderState,
328
+ dialect: SqlDialect
329
+ ): string =>
330
+ renderJsonSelectSql(
331
+ renderExpression(expression, state, dialect),
332
+ expressionDriverContext(expression, state, dialect)
333
+ )
334
+
335
+ const encodeArrayValues = (
336
+ values: readonly unknown[],
337
+ column: Table.AnyTable[typeof Table.TypeId]["fields"][string],
338
+ state: RenderState,
339
+ dialect: SqlDialect
340
+ ): readonly unknown[] =>
341
+ values.map((value) =>
342
+ toDriverValue(value, {
343
+ dialect: dialect.name,
344
+ valueMappings: state.valueMappings,
345
+ dbType: column.metadata.dbType,
346
+ runtimeSchema: column.schema,
347
+ driverValueMapping: column.metadata.driverValueMapping
348
+ }))
349
+
302
350
  const renderPostgresJsonKind = (
303
351
  value: Expression.Any
304
352
  ): "json" | "jsonb" => value[Expression.TypeId].dbType.kind === "jsonb" ? "jsonb" : "json"
@@ -460,7 +508,7 @@ const renderJsonExpression = (
460
508
  : []
461
509
  const renderedEntries = entries.flatMap((entry) => [
462
510
  dialect.renderLiteral(entry.key, state),
463
- renderExpression(entry.value, state, dialect)
511
+ renderJsonInputExpression(entry.value, state, dialect)
464
512
  ])
465
513
  if (dialect.name === "postgres") {
466
514
  return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_object(${renderedEntries.join(", ")})`
@@ -474,7 +522,7 @@ const renderJsonExpression = (
474
522
  const values = Array.isArray((ast as { readonly values?: readonly Expression.Any[] }).values)
475
523
  ? (ast as { readonly values: readonly Expression.Any[] }).values
476
524
  : []
477
- const renderedValues = values.map((value) => renderExpression(value, state, dialect)).join(", ")
525
+ const renderedValues = values.map((value) => renderJsonInputExpression(value, state, dialect)).join(", ")
478
526
  if (dialect.name === "postgres") {
479
527
  return `${postgresExpressionKind === "jsonb" ? "jsonb" : "json"}_build_array(${renderedValues})`
480
528
  }
@@ -488,7 +536,7 @@ const renderJsonExpression = (
488
536
  return undefined
489
537
  }
490
538
  if (dialect.name === "postgres") {
491
- return `to_json(${renderExpression(base, state, dialect)})`
539
+ return `to_json(${renderJsonInputExpression(base, state, dialect)})`
492
540
  }
493
541
  if (dialect.name === "mysql") {
494
542
  return `cast(${renderExpression(base, state, dialect)} as json)`
@@ -499,7 +547,7 @@ const renderJsonExpression = (
499
547
  return undefined
500
548
  }
501
549
  if (dialect.name === "postgres") {
502
- return `to_jsonb(${renderExpression(base, state, dialect)})`
550
+ return `to_jsonb(${renderJsonInputExpression(base, state, dialect)})`
503
551
  }
504
552
  if (dialect.name === "mysql") {
505
553
  return `cast(${renderExpression(base, state, dialect)} as json)`
@@ -753,7 +801,7 @@ const renderSelectionList = (
753
801
  const flattened = flattenSelection(selection)
754
802
  const projections = selectionProjections(selection)
755
803
  const sql = flattened.map(({ expression, alias }) =>
756
- `${renderExpression(expression, state, dialect)} as ${dialect.quoteIdentifier(alias)}`).join(", ")
804
+ `${renderSelectSql(renderExpression(expression, state, dialect), expressionDriverContext(expression, state, dialect))} as ${dialect.quoteIdentifier(alias)}`).join(", ")
757
805
  return {
758
806
  sql,
759
807
  projections
@@ -874,14 +922,18 @@ export const renderQueryAst = (
874
922
  const table = targetSource.source as Table.AnyTable
875
923
  const fields = table[Table.TypeId].fields
876
924
  const rendered = unnestSource.values.map((entry) =>
877
- `cast(${dialect.renderLiteral(entry.values, state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
925
+ `cast(${dialect.renderLiteral(encodeArrayValues(entry.values, fields[entry.columnName]!, state, dialect), state)} as ${renderCastType(dialect, fields[entry.columnName]!.metadata.dbType)}[])`
878
926
  ).join(", ")
879
927
  sql += ` (${columns}) select * from unnest(${rendered})`
880
928
  } else {
881
929
  const rowCount = unnestSource.values[0]?.values.length ?? 0
882
930
  const rows = Array.from({ length: rowCount }, (_, index) =>
883
931
  `(${unnestSource.values.map((entry) =>
884
- dialect.renderLiteral(entry.values[index], state)
932
+ dialect.renderLiteral(
933
+ entry.values[index],
934
+ state,
935
+ (targetSource.source as Table.AnyTable)[Table.TypeId].fields[entry.columnName]![Expression.TypeId]
936
+ )
885
937
  ).join(", ")})`
886
938
  ).join(", ")
887
939
  sql += ` (${columns}) values ${rows}`
@@ -1253,7 +1305,7 @@ export const renderExpression = (
1253
1305
  ? dialect.quoteIdentifier(ast.columnName)
1254
1306
  : `${dialect.quoteIdentifier(ast.tableName)}.${dialect.quoteIdentifier(ast.columnName)}`
1255
1307
  case "literal":
1256
- return dialect.renderLiteral(ast.value, state)
1308
+ return dialect.renderLiteral(ast.value, state, expression[Expression.TypeId])
1257
1309
  case "excluded":
1258
1310
  return dialect.name === "mysql"
1259
1311
  ? `values(${dialect.quoteIdentifier(ast.columnName)})`
@@ -1,4 +1,5 @@
1
1
  import * as CoreRenderer from "../internal/renderer.js"
2
+ import type * as Expression from "../internal/scalar.js"
2
3
  import { renderPostgresPlan } from "./internal/renderer.js"
3
4
 
4
5
  /** Postgres-specialized rendered query shape. */
@@ -8,12 +9,16 @@ export type RowOf<Value extends RenderedQuery<any>> = CoreRenderer.RowOf<Value>
8
9
  /** Postgres-specialized renderer contract. */
9
10
  export type Renderer = CoreRenderer.Renderer<"postgres">
10
11
 
12
+ export interface MakeOptions {
13
+ readonly valueMappings?: Expression.DriverValueMappings
14
+ }
15
+
11
16
  export { TypeId } from "../internal/renderer.js"
12
17
  export type { Projection } from "../internal/renderer.js"
13
18
 
14
19
  /** Creates the built-in Postgres renderer. */
15
- export const make = (): Renderer =>
16
- CoreRenderer.make("postgres", renderPostgresPlan)
20
+ export const make = (options: MakeOptions = {}): Renderer =>
21
+ CoreRenderer.make("postgres", (plan) => renderPostgresPlan(plan, options))
17
22
 
18
23
  /** Shared built-in Postgres renderer instance. */
19
24
  export const postgres = make()
@@ -25,6 +25,10 @@ type PostgresTypeNamespace = typeof postgresDatatypes & {
25
25
  readonly enum: <Kind extends string>(kind: Kind) => Expression.DbType.Enum<"postgres", Kind>
26
26
  readonly set: <Kind extends string>(kind: Kind) => Expression.DbType.Set<"postgres", Kind>
27
27
  readonly custom: <Kind extends string>(kind: Kind) => Expression.DbType.Base<"postgres", Kind>
28
+ readonly driverValueMapping: <Db extends Expression.DbType.Any>(
29
+ dbType: Db,
30
+ mapping: Expression.DriverValueMapping
31
+ ) => Db
28
32
  }
29
33
 
30
34
  /** Postgres database-type constructors for casts and typed column references. */