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.
Files changed (65) hide show
  1. package/README.md +6 -1283
  2. package/dist/mysql.js +6376 -4978
  3. package/dist/postgres/metadata.js +2724 -0
  4. package/dist/postgres.js +5475 -3636
  5. package/package.json +13 -8
  6. package/src/internal/column-state.ts +88 -6
  7. package/src/internal/column.ts +569 -34
  8. package/src/internal/datatypes/define.ts +0 -30
  9. package/src/internal/executor.ts +45 -11
  10. package/src/internal/expression-ast.ts +15 -0
  11. package/src/internal/expression.ts +3 -1
  12. package/src/internal/implication-runtime.ts +171 -0
  13. package/src/internal/mysql-query.ts +7173 -0
  14. package/src/internal/mysql-renderer.ts +2 -2
  15. package/src/internal/plan.ts +14 -4
  16. package/src/internal/{query-factory.ts → postgres-query.ts} +669 -230
  17. package/src/internal/postgres-renderer.ts +2 -2
  18. package/src/internal/postgres-schema-model.ts +144 -0
  19. package/src/internal/predicate-analysis.ts +10 -0
  20. package/src/internal/predicate-context.ts +112 -36
  21. package/src/internal/predicate-formula.ts +31 -19
  22. package/src/internal/predicate-normalize.ts +177 -106
  23. package/src/internal/predicate-runtime.ts +676 -0
  24. package/src/internal/query.ts +471 -41
  25. package/src/internal/renderer.ts +2 -2
  26. package/src/internal/runtime-schema.ts +74 -20
  27. package/src/internal/schema-ddl.ts +55 -0
  28. package/src/internal/schema-derivation.ts +93 -21
  29. package/src/internal/schema-expression.ts +44 -0
  30. package/src/internal/sql-expression-renderer.ts +123 -35
  31. package/src/internal/table-options.ts +88 -7
  32. package/src/internal/table.ts +106 -42
  33. package/src/mysql/column.ts +3 -1
  34. package/src/mysql/datatypes/index.ts +17 -2
  35. package/src/mysql/executor.ts +20 -17
  36. package/src/mysql/function/aggregate.ts +6 -0
  37. package/src/mysql/function/core.ts +5 -0
  38. package/src/mysql/function/index.ts +20 -0
  39. package/src/mysql/function/json.ts +4 -0
  40. package/src/mysql/function/string.ts +6 -0
  41. package/src/mysql/function/temporal.ts +103 -0
  42. package/src/mysql/function/window.ts +7 -0
  43. package/src/mysql/private/query.ts +1 -0
  44. package/src/mysql/query.ts +6 -26
  45. package/src/mysql.ts +2 -0
  46. package/src/postgres/cast.ts +31 -0
  47. package/src/postgres/column.ts +27 -1
  48. package/src/postgres/datatypes/index.ts +40 -5
  49. package/src/postgres/executor.ts +19 -17
  50. package/src/postgres/function/aggregate.ts +6 -0
  51. package/src/postgres/function/core.ts +16 -0
  52. package/src/postgres/function/index.ts +20 -0
  53. package/src/postgres/function/json.ts +501 -0
  54. package/src/postgres/function/string.ts +6 -0
  55. package/src/postgres/function/temporal.ts +107 -0
  56. package/src/postgres/function/window.ts +7 -0
  57. package/src/postgres/metadata.ts +31 -0
  58. package/src/postgres/private/query.ts +1 -0
  59. package/src/postgres/query.ts +6 -28
  60. package/src/postgres/schema-expression.ts +16 -0
  61. package/src/postgres/schema-management.ts +204 -0
  62. package/src/postgres/schema.ts +35 -0
  63. package/src/postgres/table.ts +307 -41
  64. package/src/postgres/type.ts +4 -0
  65. package/src/postgres.ts +16 -0
@@ -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 = (expression: Expression.Any): RuntimeSchema | undefined => {
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 unionSchemas([
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<Fields extends TableFieldMap> = Simplify<{
48
- [K in keyof Fields]: SelectType<Fields[K]>
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<Fields extends TableFieldMap> = Simplify<
53
- { [K in RequiredInsertKeys<Fields>]: InsertType<Fields[K]> } &
54
- { [K in OptionalInsertKeys<Fields>]?: InsertType<Fields[K]> }
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<Fields extends TableFieldMap, PrimaryKey extends keyof Fields> = Simplify<
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>]: UpdateType<Fields[K]>
107
+ [K in UpdateKeys<Fields, PrimaryKey>]: BrandedUpdateType<Fields[K], TableName, Extract<K, string>>
61
108
  }>
62
109
  >
63
110
 
64
- const selectSchema = (column: AnyColumnDefinition): Schema.Schema.Any =>
65
- column.metadata.nullable ? Schema.NullOr(column.schema) : column.schema
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 = (column: AnyColumnDefinition): any | undefined => {
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 ? Schema.NullOr(column.schema) : column.schema
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 ? Schema.NullOr(column.schema) : column.schema
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))