effect-qb 0.16.0 → 4.0.0-beta.66

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/README.md +7 -0
  2. package/dist/mysql.js +1858 -715
  3. package/dist/postgres/metadata.js +2036 -172
  4. package/dist/postgres.js +8011 -6849
  5. package/dist/sqlite.js +8433 -0
  6. package/package.json +7 -4
  7. package/src/internal/column-state.d.ts +3 -3
  8. package/src/internal/column-state.ts +3 -3
  9. package/src/internal/column.ts +8 -8
  10. package/src/internal/derived-table.ts +29 -3
  11. package/src/internal/dialect.ts +3 -1
  12. package/src/internal/dsl-mutation-runtime.ts +173 -4
  13. package/src/internal/dsl-plan-runtime.ts +165 -20
  14. package/src/internal/dsl-query-runtime.ts +60 -6
  15. package/src/internal/dsl-transaction-ddl-runtime.ts +72 -2
  16. package/src/internal/executor.ts +100 -43
  17. package/src/internal/expression-ast.ts +3 -2
  18. package/src/internal/grouping-key.ts +141 -1
  19. package/src/internal/implication-runtime.ts +2 -1
  20. package/src/internal/json/types.ts +155 -40
  21. package/src/internal/predicate/context.ts +14 -1
  22. package/src/internal/predicate/key.ts +19 -2
  23. package/src/internal/predicate/runtime.ts +27 -3
  24. package/src/internal/query.d.ts +1 -1
  25. package/src/internal/query.ts +253 -31
  26. package/src/internal/renderer.ts +35 -2
  27. package/src/internal/runtime/driver-value-mapping.ts +60 -2
  28. package/src/internal/runtime/normalize.ts +62 -38
  29. package/src/internal/runtime/schema.ts +32 -40
  30. package/src/internal/runtime/value.ts +159 -39
  31. package/src/internal/scalar.d.ts +1 -1
  32. package/src/internal/scalar.ts +1 -1
  33. package/src/internal/schema-derivation.d.ts +12 -61
  34. package/src/internal/schema-derivation.ts +95 -43
  35. package/src/internal/table-options.ts +108 -1
  36. package/src/internal/table.d.ts +29 -22
  37. package/src/internal/table.ts +260 -53
  38. package/src/mysql/column.ts +24 -8
  39. package/src/mysql/datatypes/index.ts +21 -0
  40. package/src/mysql/errors/catalog.ts +5 -5
  41. package/src/mysql/errors/normalize.ts +2 -2
  42. package/src/mysql/executor.ts +4 -4
  43. package/src/mysql/function/temporal.ts +1 -1
  44. package/src/mysql/internal/dsl.ts +759 -235
  45. package/src/mysql/internal/renderer.ts +2 -1
  46. package/src/mysql/internal/sql-expression-renderer.ts +486 -130
  47. package/src/mysql/query.ts +9 -2
  48. package/src/mysql/table.ts +64 -35
  49. package/src/postgres/column.ts +14 -12
  50. package/src/postgres/errors/normalize.ts +2 -2
  51. package/src/postgres/executor.ts +52 -9
  52. package/src/postgres/function/core.ts +19 -1
  53. package/src/postgres/function/temporal.ts +1 -1
  54. package/src/postgres/internal/dsl.ts +705 -256
  55. package/src/postgres/internal/renderer.ts +2 -1
  56. package/src/postgres/internal/schema-ddl.ts +2 -1
  57. package/src/postgres/internal/schema-model.ts +6 -3
  58. package/src/postgres/internal/sql-expression-renderer.ts +420 -91
  59. package/src/postgres/json.ts +57 -17
  60. package/src/postgres/query.ts +9 -2
  61. package/src/postgres/schema-management.ts +92 -6
  62. package/src/postgres/schema.ts +1 -1
  63. package/src/postgres/table.ts +203 -75
  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 +6927 -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 +175 -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.16.0",
3
+ "version": "4.0.0-beta.66",
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": {
@@ -52,9 +57,7 @@
52
57
  "@typescript/native-preview": "beta"
53
58
  },
54
59
  "dependencies": {
55
- "@effect/experimental": "^0.57.0",
56
- "@effect/sql": "^0.48.0",
57
- "effect": "^3.19.3",
60
+ "effect": "4.0.0-beta.66",
58
61
  "pgsql-ast-parser": "^12.0.2"
59
62
  }
60
63
  }
@@ -70,7 +70,7 @@ export interface ColumnState<Select, Insert, Update, Db extends Expression.DbTyp
70
70
  export interface ColumnDefinition<Select, Insert, Update, Db extends Expression.DbType.Any, Nullable extends boolean, HasDefault extends boolean, Generated extends boolean, PrimaryKey extends boolean, Unique extends boolean, Ref, Dependencies extends Expression.BindingId = never> extends Pipeable, Expression.Scalar<Select, Db, Nullable extends true ? "maybe" : "never", Db["dialect"], "scalar", Dependencies> {
71
71
  readonly pipe: Pipeable["pipe"];
72
72
  readonly [ColumnTypeId]: ColumnState<Select, Insert, Update, Db, Nullable, HasDefault, Generated, PrimaryKey, Unique, Ref, Dependencies>;
73
- readonly schema: Schema.Schema<NonNullable<Select>, any, any>;
73
+ readonly schema: Schema.Schema<NonNullable<Select>>;
74
74
  readonly metadata: {
75
75
  readonly dbType: Db;
76
76
  readonly nullable: Nullable;
@@ -111,7 +111,7 @@ export type AnyColumnDefinition = ColumnDefinition<any, any, any, Expression.DbT
111
111
  /** Convenience alias for any bound column. */
112
112
  export type AnyBoundColumn = BoundColumn<any, any, any, Expression.DbType.Any, boolean, boolean, boolean, boolean, boolean, any, string, string, string>;
113
113
  /** Constructs a runtime column-definition object from schema and metadata. */
114
- export declare const makeColumnDefinition: <Select, Insert, Update, Db extends Expression.DbType.Any, Nullable extends boolean, HasDefault extends boolean, Generated extends boolean, PrimaryKey extends boolean, Unique extends boolean, Ref, Dependencies extends string = never>(schema: Schema.Schema<NonNullable<Select>, any, any>, metadata: {
114
+ export declare const makeColumnDefinition: <Select, Insert, Update, Db extends Expression.DbType.Any, Nullable extends boolean, HasDefault extends boolean, Generated extends boolean, PrimaryKey extends boolean, Unique extends boolean, Ref, Dependencies extends string = never>(schema: Schema.Schema<NonNullable<Select>>, metadata: {
115
115
  readonly dbType: Db;
116
116
  readonly nullable: Nullable;
117
117
  readonly hasDefault: HasDefault;
@@ -135,7 +135,7 @@ export declare const makeColumnDefinition: <Select, Insert, Update, Db extends E
135
135
  } | undefined;
136
136
  }) => ColumnDefinition<Select, Insert, Update, Db, Nullable, HasDefault, Generated, PrimaryKey, Unique, Ref, Dependencies>;
137
137
  export declare const remapColumnDefinition: <Select, Insert, Update, Db extends Expression.DbType.Any, Nullable extends boolean, HasDefault extends boolean, Generated extends boolean, PrimaryKey extends boolean, Unique extends boolean, Ref, Dependencies extends string = never>(column: ColumnDefinition<Select, Insert, Update, Db, Nullable, HasDefault, Generated, PrimaryKey, Unique, Ref, Dependencies>, options?: {
138
- readonly schema?: Schema.Schema.Any | undefined;
138
+ readonly schema?: Schema.Top | undefined;
139
139
  readonly metadata?: {
140
140
  readonly dbType: Db;
141
141
  readonly nullable: Nullable;
@@ -123,7 +123,7 @@ export interface ColumnDefinition<
123
123
  Ref,
124
124
  Dependencies
125
125
  >
126
- readonly schema: Schema.Schema<NonNullable<Select>, any, any>
126
+ readonly schema: Schema.Schema<NonNullable<Select>>
127
127
  readonly metadata: {
128
128
  readonly dbType: Db
129
129
  readonly nullable: Nullable
@@ -257,7 +257,7 @@ export const makeColumnDefinition = <
257
257
  Ref,
258
258
  Dependencies extends Expression.BindingId = never
259
259
  >(
260
- schema: Schema.Schema<NonNullable<Select>, any, any>,
260
+ schema: Schema.Schema<NonNullable<Select>>,
261
261
  metadata: ColumnDefinition<
262
262
  Select,
263
263
  Insert,
@@ -345,7 +345,7 @@ export const remapColumnDefinition = <
345
345
  Dependencies
346
346
  >,
347
347
  options: {
348
- readonly schema?: Schema.Schema.Any
348
+ readonly schema?: Schema.Top
349
349
  readonly metadata?: ColumnDefinition<
350
350
  Select,
351
351
  Insert,
@@ -225,21 +225,21 @@ type ForeignKeyOptions<Target extends AnyBoundColumn> = {
225
225
 
226
226
  type SchemaCompatibleColumn<
227
227
  Column extends AnyColumnDefinition,
228
- SchemaType extends Schema.Schema.Any
229
- > = [BaseSelectType<Column>] extends [Schema.Schema.Encoded<SchemaType>]
228
+ SchemaType extends Schema.Top
229
+ > = [BaseSelectType<Column>] extends [Schema.Codec.Encoded<SchemaType>]
230
230
  ? Column
231
231
  : never
232
232
 
233
233
  type ColumnSchemaOutput<
234
234
  Column extends AnyColumnDefinition,
235
- SchemaType extends Schema.Schema.Any
235
+ SchemaType extends Schema.Top
236
236
  > = IsNullable<Column> extends true
237
237
  ? Schema.Schema.Type<SchemaType> | null
238
238
  : Schema.Schema.Type<SchemaType>
239
239
 
240
240
  type ColumnWithSchema<
241
241
  Column extends AnyColumnDefinition,
242
- SchemaType extends Schema.Schema.Any
242
+ SchemaType extends Schema.Top
243
243
  > = ColumnDefinition<
244
244
  ColumnSchemaOutput<Column, SchemaType>,
245
245
  ColumnSchemaOutput<Column, SchemaType>,
@@ -376,7 +376,7 @@ const isColumnDefinitionValue = (value: unknown): value is AnyColumnDefinition =
376
376
  typeof value === "object" && value !== null && ColumnTypeId in value
377
377
 
378
378
  const primitive = <Type, Db extends Expression.DbType.Any>(
379
- schema: Schema.Schema<Type, any, any>,
379
+ schema: Schema.Schema<Type>,
380
380
  dbType: Db
381
381
  ): ColumnDefinition<Type, Type, Type, Db, false, false, false, false, false, undefined> =>
382
382
  makeColumnDefinition(schema as Schema.Schema<NonNullable<Type>>, {
@@ -401,7 +401,7 @@ type ColumnModule<
401
401
  JsonKind extends string
402
402
  > = {
403
403
  readonly custom: <
404
- SchemaType extends Schema.Schema.Any,
404
+ SchemaType extends Schema.Top,
405
405
  Db extends Expression.DbType.Any
406
406
  >(
407
407
  schema: SchemaType,
@@ -425,7 +425,7 @@ type ColumnModule<
425
425
  readonly boolean: () => ColumnDefinition<boolean, boolean, boolean, Expression.DbType.Base<Dialect, BooleanKind>, false, false, false, false, false, undefined>
426
426
  readonly date: () => ColumnDefinition<LocalDateString, LocalDateString, LocalDateString, Expression.DbType.Base<Dialect, DateKind>, false, false, false, false, false, undefined>
427
427
  readonly timestamp: () => ColumnDefinition<LocalDateTimeString, LocalDateTimeString, LocalDateTimeString, Expression.DbType.Base<Dialect, TimestampKind>, false, false, false, false, false, undefined>
428
- readonly json: <SchemaType extends Schema.Schema.Any>(
428
+ readonly json: <SchemaType extends Schema.Top>(
429
429
  schema: SchemaType
430
430
  ) => ColumnDefinition<
431
431
  Schema.Schema.Type<SchemaType>,
@@ -442,7 +442,7 @@ type ColumnModule<
442
442
  }
443
443
 
444
444
  /** Replaces a column's runtime schema while preserving its SQL type metadata. */
445
- export const schema = <SchemaType extends Schema.Schema.Any>(nextSchema: SchemaType) =>
445
+ export const schema = <SchemaType extends Schema.Top>(nextSchema: SchemaType) =>
446
446
  <Column extends AnyColumnDefinition>(
447
447
  column: SchemaCompatibleColumn<Column, SchemaType>
448
448
  ): ColumnWithSchema<Column, SchemaType> =>
@@ -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
  }
@@ -17,11 +17,13 @@ export interface RenderState {
17
17
  readonly recursive?: boolean
18
18
  }[]
19
19
  readonly cteNames: Set<string>
20
+ readonly cteSources: Map<string, unknown>
21
+ readonly rowLocalColumns?: boolean
20
22
  }
21
23
 
22
24
  export interface RenderValueContext {
23
25
  readonly dbType?: Expression.DbType.Any
24
- readonly runtimeSchema?: Schema.Schema.Any
26
+ readonly runtimeSchema?: Schema.Top
25
27
  readonly driverValueMapping?: Expression.DriverValueMapping
26
28
  }
27
29
 
@@ -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