effect-qb 0.12.3 → 0.14.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.
- package/README.md +6 -1283
- package/dist/mysql.js +6376 -4978
- package/dist/postgres/metadata.js +2724 -0
- package/dist/postgres.js +5475 -3636
- package/package.json +13 -8
- package/src/internal/column-state.ts +88 -6
- package/src/internal/column.ts +569 -34
- package/src/internal/datatypes/define.ts +0 -30
- package/src/internal/executor.ts +45 -11
- package/src/internal/expression-ast.ts +15 -0
- package/src/internal/expression.ts +3 -1
- package/src/internal/implication-runtime.ts +171 -0
- package/src/internal/mysql-query.ts +7173 -0
- package/src/internal/mysql-renderer.ts +2 -2
- package/src/internal/plan.ts +14 -4
- package/src/internal/{query-factory.ts → postgres-query.ts} +669 -230
- package/src/internal/postgres-renderer.ts +2 -2
- package/src/internal/postgres-schema-model.ts +144 -0
- package/src/internal/predicate-analysis.ts +10 -0
- package/src/internal/predicate-context.ts +112 -36
- package/src/internal/predicate-formula.ts +31 -19
- package/src/internal/predicate-normalize.ts +177 -106
- package/src/internal/predicate-runtime.ts +676 -0
- package/src/internal/query.ts +471 -41
- package/src/internal/renderer.ts +2 -2
- package/src/internal/runtime-schema.ts +74 -20
- package/src/internal/schema-ddl.ts +55 -0
- package/src/internal/schema-derivation.ts +93 -21
- package/src/internal/schema-expression.ts +44 -0
- package/src/internal/sql-expression-renderer.ts +123 -35
- package/src/internal/table-options.ts +88 -7
- package/src/internal/table.ts +106 -42
- package/src/mysql/column.ts +3 -1
- package/src/mysql/datatypes/index.ts +17 -2
- package/src/mysql/executor.ts +20 -17
- package/src/mysql/function/aggregate.ts +6 -0
- package/src/mysql/function/core.ts +5 -0
- package/src/mysql/function/index.ts +20 -0
- package/src/mysql/function/json.ts +4 -0
- package/src/mysql/function/string.ts +6 -0
- package/src/mysql/function/temporal.ts +103 -0
- package/src/mysql/function/window.ts +7 -0
- package/src/mysql/private/query.ts +1 -0
- package/src/mysql/query.ts +6 -26
- package/src/mysql.ts +2 -0
- package/src/postgres/cast.ts +31 -0
- package/src/postgres/column.ts +27 -1
- package/src/postgres/datatypes/index.ts +40 -5
- package/src/postgres/executor.ts +19 -17
- package/src/postgres/function/aggregate.ts +6 -0
- package/src/postgres/function/core.ts +16 -0
- package/src/postgres/function/index.ts +20 -0
- package/src/postgres/function/json.ts +501 -0
- package/src/postgres/function/string.ts +6 -0
- package/src/postgres/function/temporal.ts +107 -0
- package/src/postgres/function/window.ts +7 -0
- package/src/postgres/metadata.ts +31 -0
- package/src/postgres/private/query.ts +1 -0
- package/src/postgres/query.ts +6 -28
- package/src/postgres/schema-expression.ts +16 -0
- package/src/postgres/schema-management.ts +204 -0
- package/src/postgres/schema.ts +35 -0
- package/src/postgres/table.ts +307 -41
- package/src/postgres/type.ts +4 -0
- package/src/postgres.ts +16 -0
package/src/internal/renderer.ts
CHANGED
|
@@ -41,12 +41,12 @@ export type RowOf<Value extends RenderedQuery<any, any>> = Value[typeof TypeId][
|
|
|
41
41
|
*/
|
|
42
42
|
export interface Renderer<Dialect extends string = string> {
|
|
43
43
|
readonly dialect: Dialect
|
|
44
|
-
render<PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any>>(
|
|
44
|
+
render<PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
45
45
|
plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
|
|
46
46
|
): RenderedQuery<any, Dialect>
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
type CustomRender<Dialect extends string> = <PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any>>(
|
|
49
|
+
type CustomRender<Dialect extends string> = <PlanValue extends Query.QueryPlan<any, any, any, any, any, any, any, any, any, any>>(
|
|
50
50
|
plan: Query.DialectCompatiblePlan<PlanValue, Dialect>
|
|
51
51
|
) => {
|
|
52
52
|
readonly sql: string
|
|
@@ -5,6 +5,14 @@ import * as Expression from "./expression.js"
|
|
|
5
5
|
import * as ExpressionAst from "./expression-ast.js"
|
|
6
6
|
import * as Query from "./query.js"
|
|
7
7
|
import * as JsonPath from "./json/path.js"
|
|
8
|
+
import type { PredicateFormula } from "./predicate-formula.js"
|
|
9
|
+
import {
|
|
10
|
+
assumeFormulaFalse,
|
|
11
|
+
assumeFormulaTrue,
|
|
12
|
+
contradictsFormula,
|
|
13
|
+
formulaOfExpression as formulaOfExpressionRuntime,
|
|
14
|
+
impliesFormula
|
|
15
|
+
} from "./predicate-runtime.js"
|
|
8
16
|
import { flattenSelection } from "./projections.js"
|
|
9
17
|
import {
|
|
10
18
|
BigIntStringSchema,
|
|
@@ -23,6 +31,10 @@ import type { RuntimeTag } from "./datatypes/shape.js"
|
|
|
23
31
|
|
|
24
32
|
export type RuntimeSchema = Schema.Schema<any, any, any>
|
|
25
33
|
|
|
34
|
+
type SchemaContext = {
|
|
35
|
+
readonly assumptions: PredicateFormula
|
|
36
|
+
}
|
|
37
|
+
|
|
26
38
|
const schemaCache = new WeakMap<Expression.Any, RuntimeSchema | undefined>()
|
|
27
39
|
|
|
28
40
|
const stripParameterizedKind = (kind: string): string => {
|
|
@@ -284,18 +296,59 @@ const jsonCompatibleSchema = (schema: RuntimeSchema | undefined): RuntimeSchema
|
|
|
284
296
|
}
|
|
285
297
|
|
|
286
298
|
const buildStructSchema = (
|
|
287
|
-
entries: readonly { readonly key: string; readonly value: Expression.Any }[]
|
|
299
|
+
entries: readonly { readonly key: string; readonly value: Expression.Any }[],
|
|
300
|
+
context?: SchemaContext
|
|
288
301
|
): RuntimeSchema => {
|
|
289
302
|
const fields = Object.fromEntries(
|
|
290
|
-
entries.map((entry) => [entry.key, expressionRuntimeSchema(entry.value) ?? JsonValueSchema])
|
|
303
|
+
entries.map((entry) => [entry.key, expressionRuntimeSchema(entry.value, context) ?? JsonValueSchema])
|
|
291
304
|
)
|
|
292
305
|
return Schema.Struct(fields as Record<string, RuntimeSchema>)
|
|
293
306
|
}
|
|
294
307
|
|
|
295
|
-
const buildTupleSchema = (values: readonly Expression.Any[]): RuntimeSchema =>
|
|
296
|
-
Schema.Tuple(...values.map((value) => expressionRuntimeSchema(value) ?? JsonValueSchema))
|
|
308
|
+
const buildTupleSchema = (values: readonly Expression.Any[], context?: SchemaContext): RuntimeSchema =>
|
|
309
|
+
Schema.Tuple(...values.map((value) => expressionRuntimeSchema(value, context) ?? JsonValueSchema))
|
|
310
|
+
|
|
311
|
+
const deriveCaseSchema = (
|
|
312
|
+
ast: ExpressionAst.CaseNode,
|
|
313
|
+
context?: SchemaContext
|
|
314
|
+
): RuntimeSchema | undefined => {
|
|
315
|
+
if (context === undefined) {
|
|
316
|
+
return unionSchemas([
|
|
317
|
+
...ast.branches.map((branch) => expressionRuntimeSchema(branch.then)),
|
|
318
|
+
expressionRuntimeSchema(ast.else)
|
|
319
|
+
])
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const schemas: RuntimeSchema[] = []
|
|
323
|
+
let elseAssumptions = context.assumptions
|
|
324
|
+
|
|
325
|
+
for (const branch of ast.branches) {
|
|
326
|
+
const whenFormula = formulaOfExpressionRuntime(branch.when)
|
|
327
|
+
if (contradictsFormula(elseAssumptions, whenFormula)) {
|
|
328
|
+
continue
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const branchContext = { assumptions: assumeFormulaTrue(elseAssumptions, whenFormula) }
|
|
332
|
+
const branchSchema = expressionRuntimeSchema(branch.then, branchContext)
|
|
333
|
+
if (branchSchema !== undefined) {
|
|
334
|
+
schemas.push(branchSchema)
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (impliesFormula(elseAssumptions, whenFormula)) {
|
|
338
|
+
return unionSchemas(schemas)
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
elseAssumptions = assumeFormulaFalse(elseAssumptions, whenFormula)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const elseSchema = expressionRuntimeSchema(ast.else, { assumptions: elseAssumptions })
|
|
345
|
+
return unionSchemas(elseSchema === undefined ? schemas : [...schemas, elseSchema])
|
|
346
|
+
}
|
|
297
347
|
|
|
298
|
-
const deriveRuntimeSchema = (
|
|
348
|
+
const deriveRuntimeSchema = (
|
|
349
|
+
expression: Expression.Any,
|
|
350
|
+
context?: SchemaContext
|
|
351
|
+
): RuntimeSchema | undefined => {
|
|
299
352
|
const state = expression[Expression.TypeId]
|
|
300
353
|
if (state.runtimeSchema !== undefined) {
|
|
301
354
|
return state.runtimeSchema
|
|
@@ -363,28 +416,25 @@ const deriveRuntimeSchema = (expression: Expression.Any): RuntimeSchema | undefi
|
|
|
363
416
|
return Schema.Number
|
|
364
417
|
case "max":
|
|
365
418
|
case "min":
|
|
366
|
-
return expressionRuntimeSchema(ast.value)
|
|
419
|
+
return expressionRuntimeSchema(ast.value, context)
|
|
367
420
|
case "case":
|
|
368
|
-
return
|
|
369
|
-
...ast.branches.map((branch) => expressionRuntimeSchema(branch.then)),
|
|
370
|
-
expressionRuntimeSchema(ast.else)
|
|
371
|
-
])
|
|
421
|
+
return deriveCaseSchema(ast, context)
|
|
372
422
|
case "coalesce":
|
|
373
|
-
return unionSchemas(ast.values.map(expressionRuntimeSchema))
|
|
423
|
+
return unionSchemas(ast.values.map((value) => expressionRuntimeSchema(value, context)))
|
|
374
424
|
case "scalarSubquery":
|
|
375
425
|
{
|
|
376
426
|
const selection = firstSelectedExpression(ast.plan)
|
|
377
|
-
return selection === undefined ? undefined : expressionRuntimeSchema(selection)
|
|
427
|
+
return selection === undefined ? undefined : expressionRuntimeSchema(selection, context)
|
|
378
428
|
}
|
|
379
429
|
case "window":
|
|
380
430
|
return ast.function === "over" && ast.value !== undefined
|
|
381
|
-
? expressionRuntimeSchema(ast.value)
|
|
431
|
+
? expressionRuntimeSchema(ast.value, context)
|
|
382
432
|
: Schema.Number
|
|
383
433
|
case "jsonGet":
|
|
384
434
|
case "jsonPath":
|
|
385
435
|
case "jsonAccess":
|
|
386
436
|
case "jsonTraverse": {
|
|
387
|
-
const baseSchema = expressionRuntimeSchema(ast.base
|
|
437
|
+
const baseSchema = expressionRuntimeSchema(ast.base!, context)
|
|
388
438
|
const segments = ast.segments
|
|
389
439
|
if (baseSchema === undefined || segments === undefined || !exactJsonSegments(segments)) {
|
|
390
440
|
return JsonValueSchema
|
|
@@ -397,27 +447,31 @@ const deriveRuntimeSchema = (expression: Expression.Any): RuntimeSchema | undefi
|
|
|
397
447
|
case "jsonRemove":
|
|
398
448
|
case "jsonSet":
|
|
399
449
|
case "jsonInsert":
|
|
400
|
-
return expressionRuntimeSchema(ast.base
|
|
450
|
+
return expressionRuntimeSchema(ast.base!, context)
|
|
401
451
|
case "jsonStripNulls":
|
|
402
|
-
return expressionRuntimeSchema(ast.value
|
|
452
|
+
return expressionRuntimeSchema(ast.value!, context)
|
|
403
453
|
case "jsonConcat":
|
|
404
454
|
case "jsonMerge":
|
|
405
455
|
return JsonValueSchema
|
|
406
456
|
case "jsonBuildObject":
|
|
407
|
-
return buildStructSchema(ast.entries ?? [])
|
|
457
|
+
return buildStructSchema(ast.entries ?? [], context)
|
|
408
458
|
case "jsonBuildArray":
|
|
409
|
-
return buildTupleSchema(ast.values ?? [])
|
|
459
|
+
return buildTupleSchema(ast.values ?? [], context)
|
|
410
460
|
case "jsonToJson":
|
|
411
461
|
case "jsonToJsonb":
|
|
412
|
-
return jsonCompatibleSchema(expressionRuntimeSchema(ast.value
|
|
462
|
+
return jsonCompatibleSchema(expressionRuntimeSchema(ast.value!, context))
|
|
413
463
|
case "jsonKeys":
|
|
414
464
|
return Schema.Array(Schema.String)
|
|
415
465
|
}
|
|
416
466
|
}
|
|
417
467
|
|
|
418
468
|
export const expressionRuntimeSchema = (
|
|
419
|
-
expression: Expression.Any
|
|
469
|
+
expression: Expression.Any,
|
|
470
|
+
context?: SchemaContext
|
|
420
471
|
): RuntimeSchema | undefined => {
|
|
472
|
+
if (context !== undefined) {
|
|
473
|
+
return deriveRuntimeSchema(expression, context) ?? runtimeSchemaForDbType(expression[Expression.TypeId].dbType)
|
|
474
|
+
}
|
|
421
475
|
const cached = schemaCache.get(expression)
|
|
422
476
|
if (cached !== undefined || schemaCache.has(expression)) {
|
|
423
477
|
return cached
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type * as Expression from "./expression.js"
|
|
2
|
+
import type { RenderState, SqlDialect } from "./dialect.js"
|
|
3
|
+
import { postgresDialect } from "./postgres-dialect.js"
|
|
4
|
+
import * as SchemaExpression from "./schema-expression.js"
|
|
5
|
+
import { renderExpression } from "./sql-expression-renderer.js"
|
|
6
|
+
import type { DdlExpressionLike } from "./table-options.js"
|
|
7
|
+
import { parse, toSql } from "pgsql-ast-parser"
|
|
8
|
+
|
|
9
|
+
export const renderDdlExpression = (
|
|
10
|
+
expression: DdlExpressionLike,
|
|
11
|
+
state: RenderState,
|
|
12
|
+
dialect: SqlDialect
|
|
13
|
+
): string =>
|
|
14
|
+
SchemaExpression.isSchemaExpression(expression)
|
|
15
|
+
? SchemaExpression.render(expression)
|
|
16
|
+
: renderExpression(expression as Expression.Any, state, dialect)
|
|
17
|
+
|
|
18
|
+
const escapeString = (value: string): string => `'${value.replaceAll("'", "''")}'`
|
|
19
|
+
|
|
20
|
+
const inlineLiteralDialect: SqlDialect<"postgres"> = {
|
|
21
|
+
...postgresDialect,
|
|
22
|
+
renderLiteral(value) {
|
|
23
|
+
if (value === null) {
|
|
24
|
+
return "null"
|
|
25
|
+
}
|
|
26
|
+
if (typeof value === "boolean") {
|
|
27
|
+
return value ? "true" : "false"
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === "number" || typeof value === "bigint") {
|
|
30
|
+
return String(value)
|
|
31
|
+
}
|
|
32
|
+
if (value instanceof Date) {
|
|
33
|
+
return escapeString(value.toISOString())
|
|
34
|
+
}
|
|
35
|
+
return escapeString(String(value))
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export const renderDdlExpressionSql = (expression: DdlExpressionLike): string =>
|
|
40
|
+
SchemaExpression.isSchemaExpression(expression)
|
|
41
|
+
? SchemaExpression.render(expression)
|
|
42
|
+
: renderExpression(expression as Expression.Any, {
|
|
43
|
+
params: [],
|
|
44
|
+
ctes: [],
|
|
45
|
+
cteNames: new Set()
|
|
46
|
+
}, inlineLiteralDialect)
|
|
47
|
+
|
|
48
|
+
export const normalizeDdlExpressionSql = (expression: DdlExpressionLike): string => {
|
|
49
|
+
const rendered = renderDdlExpressionSql(expression)
|
|
50
|
+
try {
|
|
51
|
+
return toSql.expr(parse(rendered, "expr"))
|
|
52
|
+
} catch {
|
|
53
|
+
return rendered.trim()
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as VariantSchema from "@effect/experimental/VariantSchema"
|
|
2
|
+
import type * as Brand from "effect/Brand"
|
|
2
3
|
import * as Schema from "effect/Schema"
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -43,43 +44,112 @@ type UpdateKeys<Fields extends TableFieldMap, PrimaryKey extends keyof Fields> =
|
|
|
43
44
|
|
|
44
45
|
type Simplify<T> = { [K in keyof T]: T[K] } & {}
|
|
45
46
|
|
|
47
|
+
type BrandedValue<
|
|
48
|
+
Value,
|
|
49
|
+
BrandName extends string
|
|
50
|
+
> = [Extract<Value, null | undefined>] extends [never]
|
|
51
|
+
? Value & Brand.Brand<BrandName>
|
|
52
|
+
: Exclude<Value, null | undefined> & Brand.Brand<BrandName> | Extract<Value, null | undefined>
|
|
53
|
+
|
|
54
|
+
type BrandNameOf<
|
|
55
|
+
TableName extends string,
|
|
56
|
+
ColumnName extends string
|
|
57
|
+
> = `${TableName}.${ColumnName}`
|
|
58
|
+
|
|
59
|
+
type BrandedSelectType<
|
|
60
|
+
Column extends AnyColumnDefinition,
|
|
61
|
+
TableName extends string,
|
|
62
|
+
ColumnName extends string
|
|
63
|
+
> = Column["metadata"]["brand"] extends true
|
|
64
|
+
? BrandedValue<SelectType<Column>, BrandNameOf<TableName, ColumnName>>
|
|
65
|
+
: SelectType<Column>
|
|
66
|
+
|
|
67
|
+
type BrandedInsertType<
|
|
68
|
+
Column extends AnyColumnDefinition,
|
|
69
|
+
TableName extends string,
|
|
70
|
+
ColumnName extends string
|
|
71
|
+
> = Column["metadata"]["brand"] extends true
|
|
72
|
+
? BrandedValue<InsertType<Column>, BrandNameOf<TableName, ColumnName>>
|
|
73
|
+
: InsertType<Column>
|
|
74
|
+
|
|
75
|
+
type BrandedUpdateType<
|
|
76
|
+
Column extends AnyColumnDefinition,
|
|
77
|
+
TableName extends string,
|
|
78
|
+
ColumnName extends string
|
|
79
|
+
> = Column["metadata"]["brand"] extends true
|
|
80
|
+
? BrandedValue<UpdateType<Column>, BrandNameOf<TableName, ColumnName>>
|
|
81
|
+
: UpdateType<Column>
|
|
82
|
+
|
|
46
83
|
/** Row shape returned by selecting from a table. */
|
|
47
|
-
export type SelectRow<
|
|
48
|
-
|
|
84
|
+
export type SelectRow<
|
|
85
|
+
TableName extends string,
|
|
86
|
+
Fields extends TableFieldMap
|
|
87
|
+
> = Simplify<{
|
|
88
|
+
[K in keyof Fields]: BrandedSelectType<Fields[K], TableName, Extract<K, string>>
|
|
49
89
|
}>
|
|
50
90
|
|
|
51
91
|
/** Insert payload derived from a table field map. */
|
|
52
|
-
export type InsertRow<
|
|
53
|
-
|
|
54
|
-
|
|
92
|
+
export type InsertRow<
|
|
93
|
+
TableName extends string,
|
|
94
|
+
Fields extends TableFieldMap
|
|
95
|
+
> = Simplify<
|
|
96
|
+
{ [K in RequiredInsertKeys<Fields>]: BrandedInsertType<Fields[K], TableName, Extract<K, string>> } &
|
|
97
|
+
{ [K in OptionalInsertKeys<Fields>]?: BrandedInsertType<Fields[K], TableName, Extract<K, string>> }
|
|
55
98
|
>
|
|
56
99
|
|
|
57
100
|
/** Update payload derived from a table field map and primary key. */
|
|
58
|
-
export type UpdateRow<
|
|
101
|
+
export type UpdateRow<
|
|
102
|
+
TableName extends string,
|
|
103
|
+
Fields extends TableFieldMap,
|
|
104
|
+
PrimaryKey extends keyof Fields
|
|
105
|
+
> = Simplify<
|
|
59
106
|
Partial<{
|
|
60
|
-
[K in UpdateKeys<Fields, PrimaryKey>]:
|
|
107
|
+
[K in UpdateKeys<Fields, PrimaryKey>]: BrandedUpdateType<Fields[K], TableName, Extract<K, string>>
|
|
61
108
|
}>
|
|
62
109
|
>
|
|
63
110
|
|
|
64
|
-
const
|
|
65
|
-
column
|
|
111
|
+
const maybeBrandSchema = (
|
|
112
|
+
column: AnyColumnDefinition,
|
|
113
|
+
tableName: string,
|
|
114
|
+
columnName: string
|
|
115
|
+
): Schema.Schema.Any =>
|
|
116
|
+
column.metadata.brand === true
|
|
117
|
+
? Schema.brand(`${tableName}.${columnName}`)(column.schema)
|
|
118
|
+
: column.schema
|
|
119
|
+
|
|
120
|
+
const selectSchema = (
|
|
121
|
+
column: AnyColumnDefinition,
|
|
122
|
+
tableName: string,
|
|
123
|
+
columnName: string
|
|
124
|
+
): Schema.Schema.Any =>
|
|
125
|
+
column.metadata.nullable ? Schema.NullOr(maybeBrandSchema(column, tableName, columnName)) : maybeBrandSchema(column, tableName, columnName)
|
|
66
126
|
|
|
67
|
-
const insertSchema = (
|
|
127
|
+
const insertSchema = (
|
|
128
|
+
column: AnyColumnDefinition,
|
|
129
|
+
tableName: string,
|
|
130
|
+
columnName: string
|
|
131
|
+
): any | undefined => {
|
|
68
132
|
if (column.metadata.generated) {
|
|
69
133
|
return undefined
|
|
70
134
|
}
|
|
71
|
-
const base = column.metadata.nullable
|
|
135
|
+
const base = column.metadata.nullable
|
|
136
|
+
? Schema.NullOr(maybeBrandSchema(column, tableName, columnName))
|
|
137
|
+
: maybeBrandSchema(column, tableName, columnName)
|
|
72
138
|
return column.metadata.nullable || column.metadata.hasDefault ? Schema.optional(base) : base
|
|
73
139
|
}
|
|
74
140
|
|
|
75
141
|
const updateSchema = (
|
|
76
142
|
column: AnyColumnDefinition,
|
|
143
|
+
tableName: string,
|
|
144
|
+
columnName: string,
|
|
77
145
|
isPrimaryKey: boolean
|
|
78
146
|
): any | undefined => {
|
|
79
147
|
if (column.metadata.generated || isPrimaryKey) {
|
|
80
148
|
return undefined
|
|
81
149
|
}
|
|
82
|
-
const base = column.metadata.nullable
|
|
150
|
+
const base = column.metadata.nullable
|
|
151
|
+
? Schema.NullOr(maybeBrandSchema(column, tableName, columnName))
|
|
152
|
+
: maybeBrandSchema(column, tableName, columnName)
|
|
83
153
|
return Schema.optional(base)
|
|
84
154
|
}
|
|
85
155
|
|
|
@@ -90,26 +160,28 @@ const updateSchema = (
|
|
|
90
160
|
* real runtime schemas.
|
|
91
161
|
*/
|
|
92
162
|
export const deriveSchemas = <
|
|
163
|
+
TableName extends string,
|
|
93
164
|
Fields extends TableFieldMap,
|
|
94
165
|
PrimaryKeyColumns extends keyof Fields & string
|
|
95
166
|
>(
|
|
167
|
+
tableName: TableName,
|
|
96
168
|
fields: Fields,
|
|
97
169
|
primaryKeyColumns: readonly PrimaryKeyColumns[]
|
|
98
170
|
): {
|
|
99
|
-
readonly select: Schema.Schema<SelectRow<Fields>>
|
|
100
|
-
readonly insert: Schema.Schema<InsertRow<Fields>>
|
|
101
|
-
readonly update: Schema.Schema<UpdateRow<Fields, PrimaryKeyColumns>>
|
|
171
|
+
readonly select: Schema.Schema<SelectRow<TableName, Fields>>
|
|
172
|
+
readonly insert: Schema.Schema<InsertRow<TableName, Fields>>
|
|
173
|
+
readonly update: Schema.Schema<UpdateRow<TableName, Fields, PrimaryKeyColumns>>
|
|
102
174
|
} => {
|
|
103
175
|
const primaryKeySet = new Set<string>(primaryKeyColumns)
|
|
104
176
|
const variants: Record<string, VariantSchema.Field<any>> = {}
|
|
105
177
|
for (const [key, column] of Object.entries(fields)) {
|
|
106
178
|
const config: Record<Variants, any> = {
|
|
107
|
-
select: selectSchema(column),
|
|
179
|
+
select: selectSchema(column, tableName, key),
|
|
108
180
|
insert: undefined,
|
|
109
181
|
update: undefined
|
|
110
182
|
}
|
|
111
|
-
const insert = insertSchema(column)
|
|
112
|
-
const update = updateSchema(column, primaryKeySet.has(key))
|
|
183
|
+
const insert = insertSchema(column, tableName, key)
|
|
184
|
+
const update = updateSchema(column, tableName, key, primaryKeySet.has(key))
|
|
113
185
|
if (insert !== undefined) {
|
|
114
186
|
config.insert = insert
|
|
115
187
|
} else {
|
|
@@ -124,8 +196,8 @@ export const deriveSchemas = <
|
|
|
124
196
|
}
|
|
125
197
|
const struct = TableSchema.Struct(variants as any)
|
|
126
198
|
return {
|
|
127
|
-
select: TableSchema.extract(struct, "select") as unknown as Schema.Schema<SelectRow<Fields>>,
|
|
128
|
-
insert: TableSchema.extract(struct, "insert") as unknown as Schema.Schema<InsertRow<Fields>>,
|
|
129
|
-
update: TableSchema.extract(struct, "update") as unknown as Schema.Schema<UpdateRow<Fields, PrimaryKeyColumns>>
|
|
199
|
+
select: TableSchema.extract(struct, "select") as unknown as Schema.Schema<SelectRow<TableName, Fields>>,
|
|
200
|
+
insert: TableSchema.extract(struct, "insert") as unknown as Schema.Schema<InsertRow<TableName, Fields>>,
|
|
201
|
+
update: TableSchema.extract(struct, "update") as unknown as Schema.Schema<UpdateRow<TableName, Fields, PrimaryKeyColumns>>
|
|
130
202
|
}
|
|
131
203
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { parse, toSql, type Expr } from "pgsql-ast-parser"
|
|
2
|
+
import { pipeArguments, type Pipeable } from "effect/Pipeable"
|
|
3
|
+
|
|
4
|
+
export const TypeId: unique symbol = Symbol.for("effect-qb/SchemaExpression")
|
|
5
|
+
|
|
6
|
+
export type TypeId = typeof TypeId
|
|
7
|
+
|
|
8
|
+
const SchemaExpressionProto = {
|
|
9
|
+
pipe(this: unknown) {
|
|
10
|
+
return pipeArguments(this, arguments)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface SchemaExpression extends Pipeable {
|
|
15
|
+
readonly [TypeId]: {
|
|
16
|
+
readonly dialect: "postgres"
|
|
17
|
+
readonly ast: Expr
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type Any = SchemaExpression
|
|
22
|
+
|
|
23
|
+
export const isSchemaExpression = (value: unknown): value is SchemaExpression =>
|
|
24
|
+
typeof value === "object" && value !== null && TypeId in value
|
|
25
|
+
|
|
26
|
+
export const fromAst = (ast: Expr): SchemaExpression => {
|
|
27
|
+
const expression = Object.create(SchemaExpressionProto)
|
|
28
|
+
expression[TypeId] = {
|
|
29
|
+
dialect: "postgres",
|
|
30
|
+
ast
|
|
31
|
+
}
|
|
32
|
+
return expression
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const parseExpression = (sql: string): SchemaExpression =>
|
|
36
|
+
fromAst(parse(sql, "expr"))
|
|
37
|
+
|
|
38
|
+
export const toAst = (expression: SchemaExpression): Expr => expression[TypeId].ast
|
|
39
|
+
|
|
40
|
+
export const render = (expression: SchemaExpression): string =>
|
|
41
|
+
toSql.expr(expression[TypeId].ast)
|
|
42
|
+
|
|
43
|
+
export const normalize = (expression: SchemaExpression): SchemaExpression =>
|
|
44
|
+
parseExpression(render(expression))
|