effect-qb 0.15.0 → 0.17.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 (88) hide show
  1. package/dist/mysql.js +1957 -595
  2. package/dist/postgres/metadata.js +2507 -182
  3. package/dist/postgres.js +9587 -8201
  4. package/dist/sqlite.js +8360 -0
  5. package/package.json +7 -2
  6. package/src/internal/column-state.ts +7 -0
  7. package/src/internal/column.ts +22 -0
  8. package/src/internal/derived-table.ts +29 -3
  9. package/src/internal/dialect.ts +14 -1
  10. package/src/internal/dsl-mutation-runtime.ts +173 -4
  11. package/src/internal/dsl-plan-runtime.ts +165 -20
  12. package/src/internal/dsl-query-runtime.ts +60 -6
  13. package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
  14. package/src/internal/executor.ts +62 -13
  15. package/src/internal/expression-ast.ts +3 -2
  16. package/src/internal/grouping-key.ts +141 -1
  17. package/src/internal/implication-runtime.ts +2 -1
  18. package/src/internal/json/types.ts +155 -40
  19. package/src/internal/predicate/analysis.ts +103 -1
  20. package/src/internal/predicate/atom.ts +7 -0
  21. package/src/internal/predicate/context.ts +170 -17
  22. package/src/internal/predicate/key.ts +64 -2
  23. package/src/internal/predicate/normalize.ts +115 -34
  24. package/src/internal/predicate/runtime.ts +144 -13
  25. package/src/internal/query.ts +563 -103
  26. package/src/internal/renderer.ts +39 -2
  27. package/src/internal/runtime/driver-value-mapping.ts +244 -0
  28. package/src/internal/runtime/normalize.ts +62 -38
  29. package/src/internal/runtime/schema.ts +5 -3
  30. package/src/internal/runtime/value.ts +153 -30
  31. package/src/internal/scalar.ts +11 -0
  32. package/src/internal/table-options.ts +108 -1
  33. package/src/internal/table.ts +87 -29
  34. package/src/mysql/column.ts +19 -2
  35. package/src/mysql/datatypes/index.ts +21 -0
  36. package/src/mysql/errors/catalog.ts +5 -5
  37. package/src/mysql/errors/normalize.ts +2 -2
  38. package/src/mysql/executor.ts +20 -5
  39. package/src/mysql/internal/dialect.ts +12 -6
  40. package/src/mysql/internal/dsl.ts +995 -263
  41. package/src/mysql/internal/renderer.ts +13 -3
  42. package/src/mysql/internal/sql-expression-renderer.ts +530 -128
  43. package/src/mysql/query.ts +9 -2
  44. package/src/mysql/renderer.ts +7 -2
  45. package/src/mysql/table.ts +38 -12
  46. package/src/postgres/cast.ts +22 -7
  47. package/src/postgres/column.ts +5 -2
  48. package/src/postgres/errors/normalize.ts +2 -2
  49. package/src/postgres/executor.ts +68 -10
  50. package/src/postgres/function/core.ts +19 -1
  51. package/src/postgres/internal/dialect.ts +12 -6
  52. package/src/postgres/internal/dsl.ts +958 -288
  53. package/src/postgres/internal/renderer.ts +13 -3
  54. package/src/postgres/internal/schema-ddl.ts +2 -1
  55. package/src/postgres/internal/schema-model.ts +6 -3
  56. package/src/postgres/internal/sql-expression-renderer.ts +477 -96
  57. package/src/postgres/json.ts +57 -17
  58. package/src/postgres/query.ts +9 -2
  59. package/src/postgres/renderer.ts +7 -2
  60. package/src/postgres/schema-management.ts +91 -4
  61. package/src/postgres/schema.ts +1 -1
  62. package/src/postgres/table.ts +189 -53
  63. package/src/postgres/type.ts +4 -0
  64. package/src/sqlite/column.ts +128 -0
  65. package/src/sqlite/datatypes/index.ts +79 -0
  66. package/src/sqlite/datatypes/spec.ts +98 -0
  67. package/src/sqlite/errors/catalog.ts +103 -0
  68. package/src/sqlite/errors/fields.ts +19 -0
  69. package/src/sqlite/errors/index.ts +19 -0
  70. package/src/sqlite/errors/normalize.ts +229 -0
  71. package/src/sqlite/errors/requirements.ts +71 -0
  72. package/src/sqlite/errors/types.ts +29 -0
  73. package/src/sqlite/executor.ts +227 -0
  74. package/src/sqlite/function/aggregate.ts +2 -0
  75. package/src/sqlite/function/core.ts +2 -0
  76. package/src/sqlite/function/index.ts +19 -0
  77. package/src/sqlite/function/string.ts +2 -0
  78. package/src/sqlite/function/temporal.ts +100 -0
  79. package/src/sqlite/function/window.ts +2 -0
  80. package/src/sqlite/internal/dialect.ts +37 -0
  81. package/src/sqlite/internal/dsl.ts +6926 -0
  82. package/src/sqlite/internal/renderer.ts +47 -0
  83. package/src/sqlite/internal/sql-expression-renderer.ts +1821 -0
  84. package/src/sqlite/json.ts +2 -0
  85. package/src/sqlite/query.ts +196 -0
  86. package/src/sqlite/renderer.ts +24 -0
  87. package/src/sqlite/table.ts +183 -0
  88. package/src/sqlite.ts +22 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "effect-qb",
3
- "version": "0.15.0",
3
+ "version": "0.17.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -33,11 +33,16 @@
33
33
  "types": "./src/mysql.ts",
34
34
  "import": "./dist/mysql.js"
35
35
  },
36
+ "./sqlite": {
37
+ "types": "./src/sqlite.ts",
38
+ "import": "./dist/sqlite.js"
39
+ },
36
40
  "./package.json": "./package.json"
37
41
  },
38
42
  "imports": {
39
43
  "#postgres": "./src/postgres.ts",
40
44
  "#mysql": "./src/mysql.ts",
45
+ "#sqlite": "./src/sqlite.ts",
41
46
  "#internal/*": "./src/internal/*"
42
47
  },
43
48
  "publishConfig": {
@@ -49,7 +54,7 @@
49
54
  },
50
55
  "devDependencies": {
51
56
  "@types/bun": "latest",
52
- "@typescript/native-preview": "latest"
57
+ "@typescript/native-preview": "beta"
53
58
  },
54
59
  "dependencies": {
55
60
  "@effect/experimental": "^0.57.0",
@@ -76,6 +76,7 @@ export interface ColumnState<
76
76
  readonly defaultValue?: DdlExpression
77
77
  readonly generatedValue?: DdlExpression
78
78
  readonly ddlType?: string
79
+ readonly driverValueMapping?: Expression.DriverValueMapping
79
80
  readonly identity?: {
80
81
  readonly generation: "always" | "byDefault"
81
82
  }
@@ -137,6 +138,7 @@ export interface ColumnDefinition<
137
138
  readonly defaultValue?: DdlExpression
138
139
  readonly generatedValue?: DdlExpression
139
140
  readonly ddlType?: string
141
+ readonly driverValueMapping?: Expression.DriverValueMapping
140
142
  readonly identity?: {
141
143
  readonly generation: "always" | "byDefault"
142
144
  }
@@ -289,6 +291,7 @@ export const makeColumnDefinition = <
289
291
  runtime: undefined as Select,
290
292
  dbType: metadata.dbType,
291
293
  runtimeSchema: schema,
294
+ driverValueMapping: metadata.driverValueMapping,
292
295
  nullability: (metadata.nullable ? "maybe" : "never") as Nullable extends true ? "maybe" : "never",
293
296
  dialect: metadata.dbType.dialect,
294
297
  kind: "scalar",
@@ -308,6 +311,7 @@ export const makeColumnDefinition = <
308
311
  defaultValue: metadata.defaultValue,
309
312
  generatedValue: metadata.generatedValue,
310
313
  ddlType: metadata.ddlType,
314
+ driverValueMapping: metadata.driverValueMapping,
311
315
  identity: metadata.identity,
312
316
  enum: metadata.enum
313
317
  }
@@ -379,6 +383,7 @@ export const remapColumnDefinition = <
379
383
  runtime: undefined as Select,
380
384
  dbType: metadata.dbType,
381
385
  runtimeSchema: schema,
386
+ driverValueMapping: metadata.driverValueMapping,
382
387
  nullability: (metadata.nullable ? "maybe" : "never") as Nullable extends true ? "maybe" : "never",
383
388
  dialect: metadata.dbType.dialect
384
389
  }
@@ -397,6 +402,7 @@ export const remapColumnDefinition = <
397
402
  defaultValue: metadata.defaultValue,
398
403
  generatedValue: metadata.generatedValue,
399
404
  ddlType: metadata.ddlType,
405
+ driverValueMapping: metadata.driverValueMapping,
400
406
  identity: metadata.identity,
401
407
  enum: metadata.enum
402
408
  }
@@ -443,6 +449,7 @@ export const bindColumn = <
443
449
  runtime: undefined as SelectType<Column>,
444
450
  dbType: column.metadata.dbType,
445
451
  runtimeSchema: schema,
452
+ driverValueMapping: column.metadata.driverValueMapping,
446
453
  nullability: (column.metadata.nullable ? "maybe" : "never") as IsNullable<Column> extends true ? "maybe" : "never",
447
454
  dialect: column.metadata.dbType.dialect,
448
455
  kind: "scalar",
@@ -123,6 +123,20 @@ type DdlTypedColumn<Column extends AnyColumnDefinition> = ColumnDefinition<
123
123
  ReferencesOf<Column>
124
124
  > & PreserveBrand<Column>
125
125
 
126
+ type DriverValueMappedColumn<Column extends AnyColumnDefinition> = ColumnDefinition<
127
+ SelectType<Column>,
128
+ InsertType<Column>,
129
+ UpdateType<Column>,
130
+ Column[typeof ColumnTypeId]["dbType"],
131
+ IsNullable<Column>,
132
+ HasDefault<Column>,
133
+ IsGenerated<Column>,
134
+ IsPrimaryKey<Column>,
135
+ Column[typeof ColumnTypeId]["unique"],
136
+ ReferencesOf<Column>,
137
+ Column[typeof ColumnTypeId]["dependencies"]
138
+ > & PreserveBrand<Column>
139
+
126
140
  type GeneratedColumn<Column extends AnyColumnDefinition> = ColumnDefinition<
127
141
  SelectType<Column>,
128
142
  InsertType<Column>,
@@ -543,6 +557,14 @@ export const ddlType = <SqlType extends string>(sqlType: SqlType) =>
543
557
  ddlType: sqlType
544
558
  }) as DdlTypedColumn<Column>
545
559
 
560
+ /** Overrides how a column crosses the SQL driver boundary. */
561
+ export const driverValueMapping = (mapping: Expression.DriverValueMapping) =>
562
+ <Column extends AnyColumnDefinition>(column: Column): DriverValueMappedColumn<Column> =>
563
+ mapColumn(column, {
564
+ ...column.metadata,
565
+ driverValueMapping: mapping
566
+ }) as DriverValueMappedColumn<Column>
567
+
546
568
  /** Marks a column as a Postgres array type. */
547
569
  export const array = <Options extends ArrayOptions | undefined = undefined>(
548
570
  options?: Options
@@ -10,11 +10,13 @@ import {
10
10
  type LateralSource,
11
11
  type QueryPlan,
12
12
  getAst,
13
+ getQueryState,
13
14
  makeExpression,
15
+ currentRequiredList,
14
16
  type SelectionOfPlan
15
17
  } from "./query.js"
16
18
  import * as ExpressionAst from "./expression-ast.js"
17
- import { flattenSelection } from "./projections.js"
19
+ import { flattenSelection, validateProjections } from "./projections.js"
18
20
 
19
21
  const DerivedProto = {
20
22
  pipe(this: unknown) {
@@ -55,6 +57,24 @@ const setPath = (
55
57
 
56
58
  const pathAlias = (path: readonly string[]): string => path.join("__")
57
59
 
60
+ const assertSourceComplete = (
61
+ plan: QueryPlan<any, any, any, any, any, any, any, any, any, any>
62
+ ): void => {
63
+ const required = currentRequiredList(plan[Plan.TypeId].required)
64
+ if (required.length > 0) {
65
+ throw new Error(`query references sources that are not yet in scope: ${required.join(", ")}`)
66
+ }
67
+ }
68
+
69
+ const assertInlineSourceStatement = (
70
+ plan: QueryPlan<any, any, any, any, any, any, any, any, any, any>
71
+ ): void => {
72
+ const statement = getQueryState(plan).statement
73
+ if (statement !== "select" && statement !== "set") {
74
+ throw new Error("inline derived sources only accept select-like query plans")
75
+ }
76
+ }
77
+
58
78
  const reboundedColumns = <
59
79
  PlanValue extends QueryPlan<any, any, any, any, any, any, any, any, any, any>,
60
80
  Alias extends string
@@ -64,7 +84,9 @@ const reboundedColumns = <
64
84
  ): DerivedSelectionOf<SelectionOfPlan<PlanValue>, Alias> => {
65
85
  const ast = getAst(plan)
66
86
  const selection = {} as Record<string, unknown>
67
- for (const projection of flattenSelection(ast.select as Record<string, unknown>)) {
87
+ const projections = flattenSelection(ast.select as Record<string, unknown>)
88
+ validateProjections(projections)
89
+ for (const projection of projections) {
68
90
  const expectedAlias = pathAlias(projection.path)
69
91
  if (projection.alias !== expectedAlias) {
70
92
  throw new Error(
@@ -98,6 +120,8 @@ export const makeDerivedSource = <
98
120
  plan: CompletePlan<PlanValue>,
99
121
  alias: Alias
100
122
  ): DerivedSource<PlanValue, Alias> => {
123
+ assertInlineSourceStatement(plan)
124
+ assertSourceComplete(plan)
101
125
  const columns = reboundedColumns(plan, alias)
102
126
  const derived = attachPipe(Object.create(DerivedProto)) as Record<string, unknown>
103
127
  Object.assign(derived, columns)
@@ -119,6 +143,7 @@ export const makeCteSource = <
119
143
  alias: Alias,
120
144
  recursive = false
121
145
  ): CteSource<PlanValue, Alias> => {
146
+ assertSourceComplete(plan)
122
147
  const columns = reboundedColumns(plan, alias)
123
148
  const cte = attachPipe(Object.create(DerivedProto)) as Record<string, unknown>
124
149
  Object.assign(cte, columns)
@@ -140,6 +165,7 @@ export const makeLateralSource = <
140
165
  plan: PlanValue,
141
166
  alias: Alias
142
167
  ): LateralSource<PlanValue, Alias> => {
168
+ assertInlineSourceStatement(plan)
143
169
  const columns = reboundedColumns(plan, alias)
144
170
  const lateral = attachPipe(Object.create(DerivedProto)) as Record<string, unknown>
145
171
  Object.assign(lateral, columns)
@@ -148,7 +174,7 @@ export const makeLateralSource = <
148
174
  lateral.baseName = alias
149
175
  lateral.dialect = plan[Plan.TypeId].dialect
150
176
  lateral.plan = plan
151
- lateral.required = undefined as never
177
+ lateral.required = currentRequiredList(plan[Plan.TypeId].required) as never
152
178
  lateral.columns = columns
153
179
  return lateral as unknown as LateralSource<PlanValue, Alias>
154
180
  }
@@ -1,3 +1,7 @@
1
+ import type * as Schema from "effect/Schema"
2
+
3
+ import type * as Expression from "./scalar.js"
4
+
1
5
  /**
2
6
  * Mutable rendering state shared while serializing SQL for a concrete dialect.
3
7
  *
@@ -6,12 +10,21 @@
6
10
  */
7
11
  export interface RenderState {
8
12
  readonly params: unknown[]
13
+ readonly valueMappings?: Expression.DriverValueMappings
9
14
  readonly ctes: {
10
15
  readonly name: string
11
16
  readonly sql: string
12
17
  readonly recursive?: boolean
13
18
  }[]
14
19
  readonly cteNames: Set<string>
20
+ readonly cteSources: Map<string, unknown>
21
+ readonly rowLocalColumns?: boolean
22
+ }
23
+
24
+ export interface RenderValueContext {
25
+ readonly dbType?: Expression.DbType.Any
26
+ readonly runtimeSchema?: Schema.Schema.Any
27
+ readonly driverValueMapping?: Expression.DriverValueMapping
15
28
  }
16
29
 
17
30
  /**
@@ -24,7 +37,7 @@ export interface RenderState {
24
37
  export interface SqlDialect<Name extends string = string> {
25
38
  readonly name: Name
26
39
  quoteIdentifier(value: string): string
27
- renderLiteral(value: unknown, state: RenderState): string
40
+ renderLiteral(value: unknown, state: RenderState, context?: RenderValueContext): string
28
41
  renderTableReference(tableName: string, baseTableName: string, schemaName?: string): string
29
42
  renderConcat(values: readonly string[]): string
30
43
  }
@@ -1,7 +1,11 @@
1
1
  import * as Expression from "./scalar.js"
2
2
  import * as Plan from "./row-set.js"
3
+ import * as Table from "./table.js"
3
4
 
4
5
  type DslMutationRuntimeContext = {
6
+ readonly profile: {
7
+ readonly dialect: string
8
+ }
5
9
  readonly makePlan: (...args: readonly any[]) => any
6
10
  readonly getAst: (plan: any) => any
7
11
  readonly getQueryState: (plan: any) => any
@@ -14,13 +18,155 @@ type DslMutationRuntimeContext = {
14
18
  readonly buildConflictTarget: (target: any, input: any) => any
15
19
  readonly mutationTargetClauses: (target: any) => readonly any[]
16
20
  readonly mutationAvailableSources: (target: any) => Record<string, any>
17
- readonly normalizeColumnList: (columns: string | readonly string[]) => readonly string[]
21
+ readonly normalizeConflictColumns: (target: any, columns: string | readonly string[]) => readonly string[]
18
22
  readonly targetSourceDetails: (target: any) => { readonly sourceName: string; readonly sourceBaseName: string }
19
23
  readonly sourceDetails: (source: any) => { readonly sourceName: string; readonly sourceBaseName: string }
20
24
  }
21
25
 
26
+ export const expectInsertSourceKind = <
27
+ Source extends { readonly kind: string } | undefined
28
+ >(
29
+ source: Source
30
+ ): Source => {
31
+ if (
32
+ source !== undefined &&
33
+ source.kind !== "values" &&
34
+ source.kind !== "query" &&
35
+ source.kind !== "unnest"
36
+ ) {
37
+ throw new Error("Unsupported insert source kind")
38
+ }
39
+ return source
40
+ }
41
+
42
+ export const expectConflictClause = <
43
+ Conflict extends {
44
+ readonly kind: string
45
+ readonly action: string
46
+ readonly target?: { readonly kind: string }
47
+ } | undefined
48
+ >(
49
+ conflict: Conflict
50
+ ): Conflict => {
51
+ if (conflict === undefined) {
52
+ return conflict
53
+ }
54
+ if (conflict.kind !== "conflict") {
55
+ throw new Error("Unsupported conflict clause kind")
56
+ }
57
+ if (conflict.action !== "doNothing" && conflict.action !== "doUpdate") {
58
+ throw new Error("Unsupported conflict action")
59
+ }
60
+ if (
61
+ conflict.target !== undefined &&
62
+ conflict.target.kind !== "columns" &&
63
+ conflict.target.kind !== "constraint"
64
+ ) {
65
+ throw new Error("Unsupported conflict target kind")
66
+ }
67
+ return conflict
68
+ }
69
+
22
70
  export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
71
+ const aliasedSourceKinds = new Set(["derived", "cte", "lateral", "values", "unnest", "tableFunction"])
72
+ const isRecord = (value: unknown): value is Record<PropertyKey, unknown> =>
73
+ typeof value === "object" && value !== null
74
+
75
+ const isTableTarget = (target: unknown): boolean =>
76
+ typeof target === "object" && target !== null && Table.TypeId in target && Plan.TypeId in target
77
+
78
+ const hasColumnRecord = (value: Record<PropertyKey, unknown>): boolean => isRecord(value.columns)
79
+
80
+ const isAliasedSource = (source: unknown): boolean => {
81
+ if (!isRecord(source)) {
82
+ return false
83
+ }
84
+ if (isTableTarget(source)) {
85
+ return true
86
+ }
87
+ if (!("kind" in source) || !("name" in source) || !("baseName" in source)) {
88
+ return false
89
+ }
90
+ if (typeof source.kind !== "string" || !aliasedSourceKinds.has(source.kind)) {
91
+ return false
92
+ }
93
+ if (typeof source.name !== "string" || typeof source.baseName !== "string") {
94
+ return false
95
+ }
96
+ switch (source.kind) {
97
+ case "derived":
98
+ case "cte":
99
+ case "lateral":
100
+ return isRecord(source.plan) && Plan.TypeId in source.plan && hasColumnRecord(source)
101
+ case "values":
102
+ return Array.isArray(source.rows) && hasColumnRecord(source)
103
+ case "unnest":
104
+ return isRecord(source.arrays) && hasColumnRecord(source)
105
+ case "tableFunction":
106
+ return typeof source.functionName === "string" && Array.isArray(source.args) && hasColumnRecord(source)
107
+ }
108
+ return false
109
+ }
110
+
111
+ const assertMutationTarget = (target: unknown, apiName: string): void => {
112
+ if (!isTableTarget(target)) {
113
+ throw new Error(`${apiName}(...) requires table targets`)
114
+ }
115
+ }
116
+
117
+ const assertAliasedSource = (source: unknown, apiName: string): void => {
118
+ if (!isAliasedSource(source)) {
119
+ throw new Error(`${apiName}(...) requires an aliased source`)
120
+ }
121
+ }
122
+
123
+ const assertMutationTargets = (
124
+ target: unknown,
125
+ apiName: string,
126
+ options: { readonly allowMultiple?: boolean } = {}
127
+ ): void => {
128
+ const targets = Array.isArray(target) ? target : [target]
129
+ if (targets.length === 0) {
130
+ throw new Error(`${apiName}(...) requires at least one table target`)
131
+ }
132
+ if (Array.isArray(target) && targets.length === 1) {
133
+ throw new Error(`${apiName}(...) requires a table target, not a single-element target tuple`)
134
+ }
135
+ for (const entry of targets) {
136
+ assertMutationTarget(entry, apiName)
137
+ }
138
+ if (targets.length > 1 && options.allowMultiple !== true) {
139
+ throw new Error(`${apiName}(...) requires a single table target`)
140
+ }
141
+ if (targets.length > 1 && ctx.profile.dialect !== "mysql" && ctx.profile.dialect !== "sqlite") {
142
+ throw new Error(`${apiName}(...) only supports multiple mutation targets for mysql`)
143
+ }
144
+ }
145
+
146
+ const assertUniqueTargetNames = (targets: readonly { readonly tableName: string }[]): void => {
147
+ const seen = new Set<string>()
148
+ for (const target of targets) {
149
+ if (seen.has(target.tableName)) {
150
+ throw new Error(`mutation target source names must be unique: ${target.tableName}`)
151
+ }
152
+ seen.add(target.tableName)
153
+ }
154
+ }
155
+
156
+ const assertInsertSelectSource = (sourcePlan: any, selection: Record<string, unknown>): void => {
157
+ const statement = ctx.getQueryState(sourcePlan).statement
158
+ if (statement !== "select" && statement !== "set") {
159
+ throw new Error("insert sources only accept select-like query plans")
160
+ }
161
+ for (const value of Object.values(selection)) {
162
+ if (value === null || typeof value !== "object" || !(Expression.TypeId in value)) {
163
+ throw new Error("insert sources require a flat selection object")
164
+ }
165
+ }
166
+ }
167
+
23
168
  const insert = (target: any, values?: Record<string, unknown>) => {
169
+ assertMutationTargets(target, "insert")
24
170
  const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
25
171
  const assignments = values === undefined
26
172
  ? []
@@ -102,10 +248,11 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
102
248
 
103
249
  const sourcePlan = source
104
250
  const selection = sourcePlan[Plan.TypeId].selection as Record<string, Expression.Any>
251
+ assertInsertSelectSource(sourcePlan, selection)
105
252
  const columns = ctx.normalizeInsertSelectColumns(selection)
106
253
  return ctx.makePlan({
107
254
  selection: current.selection,
108
- required: ctx.currentRequiredList(sourcePlan[Plan.TypeId].required).filter((name) => name !== sourceName),
255
+ required: ctx.currentRequiredList(sourcePlan[Plan.TypeId].required),
109
256
  available: current.available,
110
257
  dialect: current.dialect
111
258
  }, {
@@ -124,18 +271,26 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
124
271
  const current = plan[Plan.TypeId]
125
272
  const currentAst = ctx.getAst(plan)
126
273
  const currentQuery = ctx.getQueryState(plan)
274
+ if (currentQuery.statement !== "insert") {
275
+ throw new Error(`onConflict(...) is not supported for ${currentQuery.statement} statements`)
276
+ }
127
277
  const insertTarget = currentAst.into!.source
128
278
  const conflictTarget = ctx.buildConflictTarget(insertTarget, target)
129
279
  const updateAssignments = options.update
130
280
  ? ctx.buildMutationAssignments(insertTarget, options.update)
131
281
  : []
282
+ if (options.update !== undefined && updateAssignments.length === 0) {
283
+ throw new Error("conflict update assignments require at least one assignment")
284
+ }
132
285
  const updateWhere = options.where === undefined
133
286
  ? undefined
134
287
  : ctx.toDialectExpression(options.where)
288
+ const targetWhere = conflictTarget.kind === "columns" ? conflictTarget.where : undefined
135
289
  const required = [
136
290
  ...ctx.currentRequiredList(current.required),
137
291
  ...updateAssignments.flatMap((entry) => Object.keys(entry.value[Expression.TypeId].dependencies)),
138
- ...(updateWhere ? Object.keys(updateWhere[Expression.TypeId].dependencies) : [])
292
+ ...(updateWhere ? Object.keys(updateWhere[Expression.TypeId].dependencies) : []),
293
+ ...(targetWhere ? Object.keys(targetWhere[Expression.TypeId].dependencies) : [])
139
294
  ].filter((name, index, list) =>
140
295
  !(name in current.available) && list.indexOf(name) === index)
141
296
  return ctx.makePlan({
@@ -156,7 +311,9 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
156
311
  }
157
312
 
158
313
  const update = (target: any, values: Record<string, unknown>) => {
314
+ assertMutationTargets(target, "update", { allowMultiple: true })
159
315
  const targets = ctx.mutationTargetClauses(target)
316
+ assertUniqueTargetNames(targets)
160
317
  const primaryTarget = targets[0]!
161
318
  const assignments = ctx.buildMutationAssignments(target, values)
162
319
  const targetNames = new Set(targets.map((entry: any) => entry.tableName))
@@ -183,9 +340,13 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
183
340
  }
184
341
 
185
342
  const upsert = (target: any, values: Record<string, unknown>, conflictColumns: string | readonly string[], updateValues?: Record<string, unknown>) => {
343
+ assertMutationTargets(target, "upsert")
186
344
  const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
187
345
  const assignments = ctx.buildMutationAssignments(target, values)
188
346
  const updateAssignments = updateValues ? ctx.buildMutationAssignments(target, updateValues) : []
347
+ if (updateValues !== undefined && updateAssignments.length === 0) {
348
+ throw new Error("upsert update assignments require at least one assignment")
349
+ }
189
350
  const required = [
190
351
  ...assignments.flatMap((entry) => Object.keys(entry.value[Expression.TypeId].dependencies)),
191
352
  ...updateAssignments.flatMap((entry) => Object.keys(entry.value[Expression.TypeId].dependencies))
@@ -215,7 +376,7 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
215
376
  kind: "conflict",
216
377
  target: {
217
378
  kind: "columns",
218
- columns: ctx.normalizeColumnList(conflictColumns) as readonly [string, ...string[]]
379
+ columns: ctx.normalizeConflictColumns(target, conflictColumns) as readonly [string, ...string[]]
219
380
  },
220
381
  action: updateAssignments.length > 0 ? "doUpdate" : "doNothing",
221
382
  values: updateAssignments.length > 0 ? updateAssignments : undefined
@@ -229,7 +390,9 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
229
390
  }
230
391
 
231
392
  const delete_ = (target: any) => {
393
+ assertMutationTargets(target, "delete", { allowMultiple: true })
232
394
  const targets = ctx.mutationTargetClauses(target)
395
+ assertUniqueTargetNames(targets)
233
396
  const primaryTarget = targets[0]!
234
397
  return ctx.makePlan({
235
398
  selection: {},
@@ -250,6 +413,7 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
250
413
  }
251
414
 
252
415
  const truncate = (target: any, options: { readonly restartIdentity?: boolean; readonly cascade?: boolean } = {}) => {
416
+ assertMutationTargets(target, "truncate")
253
417
  const { sourceName, sourceBaseName } = ctx.targetSourceDetails(target)
254
418
  return ctx.makePlan({
255
419
  selection: {},
@@ -279,8 +443,13 @@ export const makeDslMutationRuntime = (ctx: DslMutationRuntimeContext) => {
279
443
  }
280
444
 
281
445
  const merge = (target: any, source: any, on: any, options: any = {}) => {
446
+ assertMutationTargets(target, "merge")
447
+ assertAliasedSource(source, "merge")
282
448
  const { sourceName: targetName, sourceBaseName: targetBaseName } = ctx.targetSourceDetails(target)
283
449
  const { sourceName: usingName, sourceBaseName: usingBaseName } = ctx.sourceDetails(source)
450
+ if (targetName === usingName) {
451
+ throw new Error(`merge(...) source name must differ from target source name: ${targetName}`)
452
+ }
284
453
  const onExpression = ctx.toDialectExpression(on)
285
454
  const matched = options.whenMatched
286
455
  const notMatched = options.whenNotMatched