@bakery-framework/orm 2.0.0-alpha.8 → 2.0.0-rc.1
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 +9 -9
- package/package.json +3 -3
- package/src/adapters/base.ts +181 -62
- package/src/adapters/mysql.ts +29 -17
- package/src/adapters/observe.ts +11 -11
- package/src/adapters/pgsql.ts +54 -39
- package/src/adapters/registry.ts +13 -13
- package/src/adapters/sqlite.ts +175 -44
- package/src/adapters.ts +4 -4
- package/src/backup.ts +1 -1
- package/src/connection.ts +1 -1
- package/src/define.ts +22 -22
- package/src/field.ts +35 -35
- package/src/globals.d.ts +1 -1
- package/src/index.ts +4 -4
- package/src/orm/index.ts +2 -2
- package/src/orm/mutation.ts +36 -28
- package/src/orm/query.ts +92 -89
- package/src/pool.ts +5 -5
- package/src/schema-registry.ts +3 -3
- package/src/schema-util.ts +48 -17
- package/src/sync/builder.ts +32 -32
- package/src/sync/diff.ts +12 -12
- package/src/sync/engine.ts +19 -19
- package/src/sync/execute.ts +16 -16
- package/src/sync/history.ts +5 -5
- package/src/sync/index.ts +12 -12
- package/src/sync/ledger.ts +23 -23
- package/src/sync/load.ts +16 -16
- package/src/sync/plan.ts +1 -2
- package/src/sync/rename.ts +1 -1
- package/src/sync/rollback.ts +3 -3
- package/src/sync/types.ts +20 -4
- package/src/sync/view-sql.ts +9 -9
- package/templates/schema.example.ts +4 -4
package/src/field.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type * as SyncTypes from './sync/types'
|
|
|
7
7
|
*
|
|
8
8
|
* The former `value()` primitive, now private and two arguments shorter.
|
|
9
9
|
* `autoIncrement` and `primary` were positional booleans that only
|
|
10
|
-
* `Field.Primary()` ever set, and it can state them directly
|
|
10
|
+
* `Field.Primary()` ever set, and it can state them directly, which is the
|
|
11
11
|
* whole reason `value('integer', undefined, false, true, true)` was worth
|
|
12
12
|
* replacing.
|
|
13
13
|
*
|
|
@@ -27,7 +27,7 @@ function column<T, N extends boolean = false, O extends boolean = false>(
|
|
|
27
27
|
if (nullable === true || d === null) result.nullable = true
|
|
28
28
|
// The runtime object is byte-for-byte what it was before `TableDef` changed
|
|
29
29
|
// shape: `type` holds the dialect name, `optional` is type-level only. That
|
|
30
|
-
// is the whole safety argument for this change
|
|
30
|
+
// is the whole safety argument for this change: no adapter, no generated
|
|
31
31
|
// file and no stored ledger payload sees any difference.
|
|
32
32
|
return result as unknown as TableDef<T, N, O>
|
|
33
33
|
}
|
|
@@ -52,9 +52,9 @@ const isColumnValue = (v: unknown): v is ColumnValue =>
|
|
|
52
52
|
/**
|
|
53
53
|
* Resolve either calling convention to `{ table, cols }`.
|
|
54
54
|
*
|
|
55
|
-
* The string form
|
|
56
|
-
* in either argument until sync time, if then. The column form
|
|
57
|
-
* `Field.Index(posts.authorId)
|
|
55
|
+
* The string form: `Field.Index('posts', ['authorId'])`: cannot catch a typo
|
|
56
|
+
* in either argument until sync time, if then. The column form:
|
|
57
|
+
* `Field.Index(posts.authorId)`: carries its own table, so that argument
|
|
58
58
|
* disappears and a mistake becomes a compile error.
|
|
59
59
|
*/
|
|
60
60
|
function resolveTarget(
|
|
@@ -86,8 +86,8 @@ export interface ForeignKeyActions {
|
|
|
86
86
|
/**
|
|
87
87
|
* The shape `Field.Foreign()` contributes to a row type.
|
|
88
88
|
*
|
|
89
|
-
* Always `integer`, because that is what `Field.Primary()` always is
|
|
90
|
-
* `INTEGER PRIMARY KEY AUTOINCREMENT`
|
|
89
|
+
* Always `integer`, because that is what `Field.Primary()` always is (an
|
|
90
|
+
* `INTEGER PRIMARY KEY AUTOINCREMENT`), and a foreign key exists to point at
|
|
91
91
|
* one. Nullable adds `| null`, which is the only variation worth having.
|
|
92
92
|
*
|
|
93
93
|
* The runtime object still resolves its type from the referenced column (see
|
|
@@ -102,14 +102,14 @@ type ForeignDef<N extends true | undefined> = TableDef<
|
|
|
102
102
|
N extends true ? true : false
|
|
103
103
|
>
|
|
104
104
|
|
|
105
|
-
/** What `Field.Index` / `Field.Unique` accept
|
|
105
|
+
/** What `Field.Index` / `Field.Unique` accept: a column produced by `table()`. */
|
|
106
106
|
type ColumnValue = { __table: string; __column: string }
|
|
107
107
|
|
|
108
108
|
/**
|
|
109
109
|
* What `Field.Enum` accepts: a literal array, or a TypeScript string enum.
|
|
110
110
|
*
|
|
111
111
|
* A string enum is an ordinary object at runtime (`{ Draft: 'draft' }`), so
|
|
112
|
-
* both forms reduce to the same list of members
|
|
112
|
+
* both forms reduce to the same list of members, `Object.values` for the
|
|
113
113
|
* object, the tuple itself for the array.
|
|
114
114
|
*
|
|
115
115
|
* **String enums only.** A *numeric* enum compiles to an object with a reverse
|
|
@@ -127,7 +127,7 @@ type EnumValues<V> = V extends readonly (infer U extends string)[]
|
|
|
127
127
|
: V[keyof V]
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
* `Field
|
|
130
|
+
* `Field`: the column vocabulary, namespaced so it is discoverable.
|
|
131
131
|
*
|
|
132
132
|
* This replaced `value('string', null, true, false, false)`, which required
|
|
133
133
|
* remembering a type string *and* the meaning of four positional booleans.
|
|
@@ -137,8 +137,8 @@ type EnumValues<V> = V extends readonly (infer U extends string)[]
|
|
|
137
137
|
* **`Field` is now the whole vocabulary, not sugar over one.** `value`,
|
|
138
138
|
* `primary`, `index`, `unique` and `foreign` are gone; the construction they
|
|
139
139
|
* did lives in `column()` and `resolveTarget()` above, private to this file.
|
|
140
|
-
* What the sync engine and the type inference consume is unchanged
|
|
141
|
-
* descriptor objects
|
|
140
|
+
* What the sync engine and the type inference consume is unchanged (plain
|
|
141
|
+
* descriptor objects), so this moved the API without moving the contract.
|
|
142
142
|
*
|
|
143
143
|
* The one shape deliberately left unspellable is **nullable *and* defaulted to
|
|
144
144
|
* something other than null**, because a null default is how you say nullable.
|
|
@@ -158,7 +158,7 @@ type EnumValues<V> = V extends readonly (infer U extends string)[]
|
|
|
158
158
|
*/
|
|
159
159
|
export const Field = {
|
|
160
160
|
/**
|
|
161
|
-
* `INTEGER PRIMARY KEY AUTOINCREMENT
|
|
161
|
+
* `INTEGER PRIMARY KEY AUTOINCREMENT`: the id column, spelled once.
|
|
162
162
|
*
|
|
163
163
|
* The single most repeated line in any schema, and the one most likely to be
|
|
164
164
|
* written wrong by hand: `value('integer', undefined, false, true, true)`.
|
|
@@ -185,7 +185,7 @@ export const Field = {
|
|
|
185
185
|
>('integer', d),
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
|
-
* A fractional number
|
|
188
|
+
* A fractional number: `DOUBLE` on MySQL, `DOUBLE PRECISION` on Postgres,
|
|
189
189
|
* `REAL` on SQLite.
|
|
190
190
|
*
|
|
191
191
|
* Named `Float` rather than mirroring the underlying `'number'` type string,
|
|
@@ -208,12 +208,12 @@ export const Field = {
|
|
|
208
208
|
>('string', d),
|
|
209
209
|
|
|
210
210
|
/**
|
|
211
|
-
* Unbounded text
|
|
211
|
+
* Unbounded text: `TEXT` on every dialect.
|
|
212
212
|
*
|
|
213
213
|
* **MySQL rejects a literal `DEFAULT` on TEXT**, so this takes no default.
|
|
214
214
|
* That is not an omission: `Field.String('')` emits
|
|
215
215
|
* `TEXT NOT NULL DEFAULT ''`, which MySQL refuses outright with "BLOB, TEXT,
|
|
216
|
-
* GEOMETRY or JSON column can't have a default value"
|
|
216
|
+
* GEOMETRY or JSON column can't have a default value", the shipped schema
|
|
217
217
|
* template could not `db:sync` against MySQL because of exactly this. Use
|
|
218
218
|
* `Varchar` when you need a default.
|
|
219
219
|
*/
|
|
@@ -227,12 +227,12 @@ export const Field = {
|
|
|
227
227
|
},
|
|
228
228
|
|
|
229
229
|
/**
|
|
230
|
-
* Sized text
|
|
230
|
+
* Sized text: `VARCHAR(n)`, and the answer to TEXT's default problem, since
|
|
231
231
|
* every dialect accepts a default on a sized column.
|
|
232
232
|
*
|
|
233
233
|
* slug: Field.Varchar(255, ''),
|
|
234
234
|
*
|
|
235
|
-
* SQLite has no real `VARCHAR`
|
|
235
|
+
* SQLite has no real `VARCHAR` (all text is TEXT affinity), but it stores
|
|
236
236
|
* the declared type verbatim and reads it back, so one schema round-trips on
|
|
237
237
|
* all three.
|
|
238
238
|
*
|
|
@@ -254,7 +254,7 @@ export const Field = {
|
|
|
254
254
|
),
|
|
255
255
|
|
|
256
256
|
/**
|
|
257
|
-
* A 64-bit integer
|
|
257
|
+
* A 64-bit integer: `BIGINT` everywhere.
|
|
258
258
|
*
|
|
259
259
|
* Reads back as a **string** on MySQL and Postgres, which is how they avoid
|
|
260
260
|
* losing precision, and as a **number** on SQLite, which does not: values
|
|
@@ -269,11 +269,11 @@ export const Field = {
|
|
|
269
269
|
>('bigint' as any, d),
|
|
270
270
|
|
|
271
271
|
/**
|
|
272
|
-
* A JSON document
|
|
272
|
+
* A JSON document: `JSON` on MySQL, `JSONB` on Postgres, a `JSON`-declared
|
|
273
273
|
* text column on SQLite.
|
|
274
274
|
*
|
|
275
275
|
* MySQL and Postgres parse it into an object on read; SQLite hands back the
|
|
276
|
-
* raw string. The row type is therefore `unknown
|
|
276
|
+
* raw string. The row type is therefore `unknown`: narrow it where you use
|
|
277
277
|
* it rather than trusting a type that would be wrong on one of the three.
|
|
278
278
|
*
|
|
279
279
|
* Takes no default, for the same reason `Text` does not: MySQL refuses a
|
|
@@ -286,7 +286,7 @@ export const Field = {
|
|
|
286
286
|
(nullable: true): TableDef<unknown, true, true>
|
|
287
287
|
},
|
|
288
288
|
|
|
289
|
-
/** True/false
|
|
289
|
+
/** True/false: `BOOLEAN` on Postgres, `TINYINT(1)` on MySQL. */
|
|
290
290
|
Bool: <D extends boolean | null | undefined = undefined>(d?: D) =>
|
|
291
291
|
column<
|
|
292
292
|
Nullable<boolean, D extends null ? true : false>,
|
|
@@ -312,8 +312,8 @@ export const Field = {
|
|
|
312
312
|
*
|
|
313
313
|
* **The column's type is copied from the target, not declared here**, and
|
|
314
314
|
* that is the real reason to prefer this form. MySQL refuses a foreign key
|
|
315
|
-
* whose column type does not match the referenced key *exactly*
|
|
316
|
-
* `INT` child against a `BIGINT` parent is rejected outright
|
|
315
|
+
* whose column type does not match the referenced key *exactly* (an
|
|
316
|
+
* `INT` child against a `BIGINT` parent is rejected outright), and that
|
|
317
317
|
* mismatch is invisible in a schema where the two columns are declared pages
|
|
318
318
|
* apart. Resolution happens in `resolveColumnForeignKeys()`, where the whole
|
|
319
319
|
* schema is in scope, so the two cannot disagree.
|
|
@@ -332,11 +332,11 @@ export const Field = {
|
|
|
332
332
|
} = {},
|
|
333
333
|
) =>
|
|
334
334
|
({
|
|
335
|
-
// `integer` in the row type, always
|
|
335
|
+
// `integer` in the row type, always. See `ForeignDef`. At *runtime* the
|
|
336
336
|
// type is still overwritten from the referenced column by
|
|
337
337
|
// `resolveColumnForeignKeys()`, because MySQL refuses a key whose column
|
|
338
|
-
// type does not match the target exactly. For the ordinary case
|
|
339
|
-
// pointing at a `Field.Primary()
|
|
338
|
+
// type does not match the target exactly. For the ordinary case: a key
|
|
339
|
+
// pointing at a `Field.Primary()`: the two agree and there is nothing to
|
|
340
340
|
// reconcile.
|
|
341
341
|
type: 'integer',
|
|
342
342
|
...(options.nullable ? { nullable: true, default: null } : {}),
|
|
@@ -429,7 +429,7 @@ export const Field = {
|
|
|
429
429
|
* The column form carries its own table, so there is no separate table
|
|
430
430
|
* argument to get wrong; the string form exists because the `DBInfo`
|
|
431
431
|
* namespace layout has no `table()` values to point at. Several columns make
|
|
432
|
-
* one composite index, in the order given
|
|
432
|
+
* one composite index, in the order given, which is the order that decides
|
|
433
433
|
* which queries it can serve.
|
|
434
434
|
*
|
|
435
435
|
* A direct alias of `index()` rather than a wrapper, so the two cannot drift
|
|
@@ -460,7 +460,7 @@ export const Field = {
|
|
|
460
460
|
},
|
|
461
461
|
|
|
462
462
|
/**
|
|
463
|
-
* A UUID, generated by the database
|
|
463
|
+
* A UUID, generated by the database: `CHAR(36)` sized text with a
|
|
464
464
|
* per-dialect default expression.
|
|
465
465
|
*
|
|
466
466
|
* id: Field.Uuid(),
|
|
@@ -468,7 +468,7 @@ export const Field = {
|
|
|
468
468
|
* `gen_random_uuid()` on Postgres, `UUID()` on MySQL, and
|
|
469
469
|
* `lower(hex(randomblob(16)))` shaped into the canonical form on SQLite,
|
|
470
470
|
* which has no UUID function of its own. All three round-trip through the
|
|
471
|
-
* `%uuid%` marker, the same way `Field.Date.now()` round-trips `%dateNow
|
|
471
|
+
* `%uuid%` marker, the same way `Field.Date.now()` round-trips `%dateNow%`,
|
|
472
472
|
* without the read-back half the database reports its own expression, the
|
|
473
473
|
* schema says `%uuid%`, and the column rebuilds on every sync forever.
|
|
474
474
|
*
|
|
@@ -496,11 +496,11 @@ export const Field = {
|
|
|
496
496
|
*
|
|
497
497
|
* Enforced in the database too, and **the same way on all three dialects**:
|
|
498
498
|
* a `CHECK (col IN (…))`. MySQL has a native `ENUM` and this deliberately
|
|
499
|
-
* does not use it
|
|
499
|
+
* does not use it: a value rejected by MySQL and accepted by SQLite means
|
|
500
500
|
* an app that behaves differently depending on where it runs, which is worse
|
|
501
501
|
* than either choice made consistently.
|
|
502
502
|
*
|
|
503
|
-
* The members are **not part of the column diff
|
|
503
|
+
* The members are **not part of the column diff**. See
|
|
504
504
|
* `ColumnConstraint._enum`. Adding or removing one does not migrate on its
|
|
505
505
|
* own; the table has to be rebuilt for the CHECK to change.
|
|
506
506
|
*/
|
|
@@ -520,7 +520,7 @@ export const Field = {
|
|
|
520
520
|
throws(
|
|
521
521
|
'Field.Enum() takes a string enum or an array of strings. A numeric ' +
|
|
522
522
|
'enum reverse-maps its members, so its values are half names and ' +
|
|
523
|
-
'half numbers
|
|
523
|
+
'half numbers. Use Field.Int() and validate in your code, or give ' +
|
|
524
524
|
'the enum string values.',
|
|
525
525
|
)
|
|
526
526
|
}
|
|
@@ -555,7 +555,7 @@ export const Field = {
|
|
|
555
555
|
* **`updatedAt` is not auto-maintained on write, and that is a deliberate
|
|
556
556
|
* limit rather than an oversight.** Doing it silently needs the query layer
|
|
557
557
|
* to know which tables have the column, and the query layer has no runtime
|
|
558
|
-
* view of the schema at all
|
|
558
|
+
* view of the schema at all: `schema.ts` is loaded by the sync engine, not
|
|
559
559
|
* by the ORM. The alternatives were a `hasCol` probe on every UPDATE, or
|
|
560
560
|
* stamping a column that might not exist. Stamp it yourself:
|
|
561
561
|
*
|
|
@@ -570,7 +570,7 @@ export const Field = {
|
|
|
570
570
|
* The current time as Unix **seconds**, for a value position.
|
|
571
571
|
*
|
|
572
572
|
* A plain number, so it binds as an ordinary parameter and needs no dialect
|
|
573
|
-
* handling
|
|
573
|
+
* handling: unlike `%dateNow%`, which is a DDL default marker and means
|
|
574
574
|
* nothing in an INSERT or UPDATE.
|
|
575
575
|
*/
|
|
576
576
|
now: () => Math.floor(Date.now() / 1000),
|
package/src/globals.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { AppDBOptionals, AppDBSchema } from './schema-registry'
|
|
|
4
4
|
* Ambient database types.
|
|
5
5
|
*
|
|
6
6
|
* These live with the orm package rather than in core's `global.d.ts`: they are
|
|
7
|
-
* derived from this package's schema registry, and core cannot reference orm
|
|
7
|
+
* derived from this package's schema registry, and core cannot reference orm,
|
|
8
8
|
* orm depends on core, so the reverse would be circular.
|
|
9
9
|
*
|
|
10
10
|
* Both are read only inside this package (`orm/query.ts`, `orm/mutation.ts`,
|
package/src/index.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type {
|
|
|
12
12
|
*
|
|
13
13
|
* Re-exported from the root rather than given a `./observe` subpath. The
|
|
14
14
|
* export map is closed and every entry in it is public API from the moment it
|
|
15
|
-
* ships, so a new subpath needs a reason
|
|
15
|
+
* ships, so a new subpath needs a reason, and there is none here: this is one
|
|
16
16
|
* function and three types, the observer is process-wide, and an app sets it
|
|
17
17
|
* once at boot next to where it already imports `DB`.
|
|
18
18
|
*
|
|
@@ -34,15 +34,15 @@ export type {
|
|
|
34
34
|
* Schema authoring, from one place.
|
|
35
35
|
*
|
|
36
36
|
* `table`/`alias` live in `define.ts` and the column and constraint helpers in
|
|
37
|
-
* `schema-util.ts`, but that split is an implementation detail
|
|
37
|
+
* `schema-util.ts`, but that split is an implementation detail: someone
|
|
38
38
|
* writing `orm/schema.ts` should import from `@bakery-framework/orm` without having to
|
|
39
39
|
* know which file a helper happens to sit in.
|
|
40
40
|
*/
|
|
41
41
|
export { alias, table, view } from './define'
|
|
42
42
|
export { Field } from './field'
|
|
43
43
|
/**
|
|
44
|
-
* `TableDef` is the **column** descriptor
|
|
45
|
-
*
|
|
44
|
+
* `TableDef` is the **column** descriptor (`TableDef<TYPE, nullable, optional>`
|
|
45
|
+
*), and it now comes from the root barrel, which is where someone writing a
|
|
46
46
|
* schema would look for it.
|
|
47
47
|
*
|
|
48
48
|
* It did not, and that was a defect rather than an omission: the barrel used to
|
package/src/orm/index.ts
CHANGED
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
*
|
|
9
9
|
* `./query` first works. `./mutation` first does not: `mutation.ts` pulls in
|
|
10
10
|
* `query.ts`, whose top-level `Mutation.Insert` runs while `Mutation` is still
|
|
11
|
-
*
|
|
11
|
+
* initializing, and the whole ORM dies with
|
|
12
12
|
* `TypeError: undefined is not an object (evaluating 'Mutation.Insert')`.
|
|
13
13
|
*
|
|
14
14
|
* Biome's `organizeImports` sorts these alphabetically and puts `./mutation`
|
|
15
15
|
* first. The compiler cannot see the breakage, because the types are fine
|
|
16
16
|
* either way. Hence the suppression.
|
|
17
17
|
*/
|
|
18
|
-
// biome-ignore-all assist/source/organizeImports: cycle
|
|
18
|
+
// biome-ignore-all assist/source/organizeImports: cycle. See above
|
|
19
19
|
import { DB } from './query'
|
|
20
20
|
import { Mutation } from './mutation'
|
|
21
21
|
|
package/src/orm/mutation.ts
CHANGED
|
@@ -7,7 +7,7 @@ import type {
|
|
|
7
7
|
AppDBOptionals as DBOptionals,
|
|
8
8
|
AppDBSchema as DBSchema,
|
|
9
9
|
} from '../schema-registry'
|
|
10
|
-
import { evalOperands, qId } from '../schema-util'
|
|
10
|
+
import { evalOperands, nullComparison, qId } from '../schema-util'
|
|
11
11
|
import { DB } from './query'
|
|
12
12
|
|
|
13
13
|
export namespace Mutation {
|
|
@@ -16,7 +16,7 @@ export namespace Mutation {
|
|
|
16
16
|
*
|
|
17
17
|
* There used to be a `| (string & {})` member here, for autocomplete on the
|
|
18
18
|
* literals while still accepting any string. It also made the rest of the
|
|
19
|
-
* type decorative
|
|
19
|
+
* type decorative: `Exclude<…, AppViews>` never rejected anything, so a
|
|
20
20
|
* `DB.Insert.into('some_view')` compiled and failed at the database instead,
|
|
21
21
|
* and so did a typo'd table name.
|
|
22
22
|
*
|
|
@@ -36,7 +36,7 @@ export namespace Mutation {
|
|
|
36
36
|
export type Prettify<T> = { [K in keyof T]: T[K] } & {}
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
|
-
* `RETURNING` takes an identifier list and is interpolated, not bound
|
|
39
|
+
* `RETURNING` takes an identifier list and is interpolated, not bound: the
|
|
40
40
|
* same position `orderBy` and `groupBy` guard with `safeColumn`. It was the
|
|
41
41
|
* one identifier writer on Insert/Update/Delete with no guard at all, so
|
|
42
42
|
* `.returning('* FROM users; DROP TABLE t --')` was emitted verbatim.
|
|
@@ -71,10 +71,10 @@ export namespace Mutation {
|
|
|
71
71
|
: string
|
|
72
72
|
|
|
73
73
|
/**
|
|
74
|
-
* What a write returns
|
|
74
|
+
* What a write returns: the adapter's own result, not a copy of it.
|
|
75
75
|
*
|
|
76
76
|
* It *was* a copy: an identical `{lastInsertRowid, changes}` declared here as
|
|
77
|
-
* well as on `SQLAdapter`. Identical today is the whole problem
|
|
77
|
+
* well as on `SQLAdapter`. Identical today is the whole problem: that is the
|
|
78
78
|
* state `SQLAdapter.ColumnConstraint` was in before it fell behind
|
|
79
79
|
* `sync/types.ts` and started erasing fields at the cast.
|
|
80
80
|
*/
|
|
@@ -87,14 +87,14 @@ export namespace Mutation {
|
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
/**
|
|
90
|
-
* The rows to insert
|
|
90
|
+
* The rows to insert: as a spread, or as one array.
|
|
91
91
|
*
|
|
92
92
|
* Both forms exist because the variadic signature alone made the obvious
|
|
93
93
|
* call wrong in a way nothing announced: `values(rows)` bound the *array*
|
|
94
94
|
* as a single record, and the only symptom was
|
|
95
95
|
* `table big has no column named 0`. An array is never a record, so the two
|
|
96
|
-
* forms cannot be confused, and anything that is neither
|
|
97
|
-
* `values(rows, extra)`, a primitive
|
|
96
|
+
* forms cannot be confused, and anything that is neither (a mixed
|
|
97
|
+
* `values(rows, extra)`, a primitive) is rejected by name rather than
|
|
98
98
|
* turned into columns called `0` and `1`.
|
|
99
99
|
*/
|
|
100
100
|
values(records: InsertSchema<T>[]): InsertExecutable
|
|
@@ -115,7 +115,7 @@ export namespace Mutation {
|
|
|
115
115
|
'values() takes records: values(row), values(rowA, rowB) or ' +
|
|
116
116
|
'values(rows). Got a ' +
|
|
117
117
|
(Array.isArray(record) ? 'nested array' : typeof record) +
|
|
118
|
-
'
|
|
118
|
+
': if you meant to pass an array of rows, pass it as the only ' +
|
|
119
119
|
'argument.',
|
|
120
120
|
)
|
|
121
121
|
}
|
|
@@ -142,7 +142,7 @@ export namespace Mutation {
|
|
|
142
142
|
/**
|
|
143
143
|
* Insert, or update the row that is already there.
|
|
144
144
|
*
|
|
145
|
-
* `cols` names the unique columns that decide "already there"
|
|
145
|
+
* `cols` names the unique columns that decide "already there": a primary
|
|
146
146
|
* key or a unique index. Without an upsert the only way to express this is
|
|
147
147
|
* to check and then branch, which is a **race**: two requests both see no
|
|
148
148
|
* row and both insert.
|
|
@@ -150,8 +150,8 @@ export namespace Mutation {
|
|
|
150
150
|
* DB.Insert.into('users').values({ email, name }).upsert(['email'])
|
|
151
151
|
*
|
|
152
152
|
* By default every inserted column except the conflict columns is
|
|
153
|
-
* overwritten. Pass a second argument to narrow that
|
|
154
|
-
* ['name'])` leaves everything else as it was
|
|
153
|
+
* overwritten. Pass a second argument to narrow that (`upsert(['email'],
|
|
154
|
+
* ['name'])` leaves everything else as it was), or an empty array for
|
|
155
155
|
* insert-if-absent, which becomes `DO NOTHING`.
|
|
156
156
|
*
|
|
157
157
|
* MySQL ignores `cols` because `ON DUPLICATE KEY UPDATE` fires on *any*
|
|
@@ -180,7 +180,7 @@ export namespace Mutation {
|
|
|
180
180
|
* How many records fit in one statement, under the adapter's ceiling.
|
|
181
181
|
*
|
|
182
182
|
* The ceiling comes from the adapter because only it knows its dialect, but
|
|
183
|
-
* a statement can be *rendered* without a connection
|
|
183
|
+
* a statement can be *rendered* without a connection: `parse()` is how the
|
|
184
184
|
* tests read the SQL, and the stub adapter in `orm.test.ts` implements two
|
|
185
185
|
* members. An unreachable or silent adapter falls back to the same default
|
|
186
186
|
* the base class publishes rather than making `parse()` require a database.
|
|
@@ -222,7 +222,7 @@ export namespace Mutation {
|
|
|
222
222
|
}
|
|
223
223
|
|
|
224
224
|
/**
|
|
225
|
-
* The insert as one statement per batch
|
|
225
|
+
* The insert as one statement per batch: the form the executors run.
|
|
226
226
|
*
|
|
227
227
|
* A single `INSERT … VALUES (…),(…),…` carries three parameters per row, so
|
|
228
228
|
* it stops being a legal statement somewhere around eleven thousand rows.
|
|
@@ -233,15 +233,15 @@ export namespace Mutation {
|
|
|
233
233
|
* like for an arbitrary `rows`.
|
|
234
234
|
*
|
|
235
235
|
* The column list is computed once, across every record, so every batch
|
|
236
|
-
* inserts the same columns in the same order
|
|
236
|
+
* inserts the same columns in the same order: a per-batch union would
|
|
237
237
|
* change the shape of the statement halfway through the insert.
|
|
238
238
|
*
|
|
239
239
|
* **`RETURNING` is accumulated, not refused.** Refusing is the
|
|
240
240
|
* safe-looking option and the wrong one: the batches run sequentially
|
|
241
241
|
* inside one transaction, so concatenating each batch's rows in batch order
|
|
242
242
|
* reproduces the sequence a single statement would have produced. Ordering
|
|
243
|
-
* *within* a batch is whatever the dialect gives
|
|
244
|
-
* promise `RETURNING` follows `VALUES` order
|
|
243
|
+
* *within* a batch is whatever the dialect gives (Postgres does not
|
|
244
|
+
* promise `RETURNING` follows `VALUES` order), but that is equally true
|
|
245
245
|
* unchunked, so batching neither adds nor removes a guarantee. Refusing
|
|
246
246
|
* would have cost the main reason to write `.values(rows).returning('id')`
|
|
247
247
|
* at all: getting the generated ids of a bulk import back.
|
|
@@ -288,7 +288,7 @@ export namespace Mutation {
|
|
|
288
288
|
*
|
|
289
289
|
* The transaction is what keeps a batched insert meaning what the unbatched
|
|
290
290
|
* one meant: all rows or none. It is opened only when a batch boundary
|
|
291
|
-
* exists
|
|
291
|
+
* exists (one statement is already atomic), and only when the caller is
|
|
292
292
|
* not already inside `DB.transaction`, where the outer transaction is
|
|
293
293
|
* already the atomic unit.
|
|
294
294
|
*
|
|
@@ -329,7 +329,7 @@ export namespace Mutation {
|
|
|
329
329
|
* Two shapes, not three: Postgres and SQLite share
|
|
330
330
|
* `ON CONFLICT (…) DO UPDATE SET col = excluded.col`, while MySQL spells it
|
|
331
331
|
* `ON DUPLICATE KEY UPDATE col = VALUES(col)` and takes no conflict target
|
|
332
|
-
* at all
|
|
332
|
+
* at all, it fires on whichever unique key was violated.
|
|
333
333
|
*
|
|
334
334
|
* Every identifier goes through `qId`, and no value is interpolated: the
|
|
335
335
|
* new row's values are already bound as the INSERT's parameters, and both
|
|
@@ -345,7 +345,7 @@ export namespace Mutation {
|
|
|
345
345
|
).filter(k => insertedKeys.includes(k))
|
|
346
346
|
|
|
347
347
|
// The adapter spells it. This used to branch on `driver === 'mysql'`
|
|
348
|
-
// here, which put one dialect's syntax in the shared query builder
|
|
348
|
+
// here, which put one dialect's syntax in the shared query builder: the
|
|
349
349
|
// thing every other difference (quote character, placeholder ceiling,
|
|
350
350
|
// date expression, foreign-key clause) is kept out of it for.
|
|
351
351
|
return getActiveDb().upsertClause(cols, targets)
|
|
@@ -362,7 +362,7 @@ export namespace Mutation {
|
|
|
362
362
|
}
|
|
363
363
|
|
|
364
364
|
async fetch<R = any>(): Promise<R | undefined> {
|
|
365
|
-
// Every batch still runs
|
|
365
|
+
// Every batch still runs: `fetch()` means "insert, and hand me a row
|
|
366
366
|
// back", not "insert the first batch". `Executor.get` is defined as
|
|
367
367
|
// `all(…)[0]`, so this is the same row it would have produced.
|
|
368
368
|
const rows = await this.array<R>()
|
|
@@ -381,7 +381,7 @@ export namespace Mutation {
|
|
|
381
381
|
const changes = results.reduce((n, r) => n + Number(r?.changes ?? 0), 0)
|
|
382
382
|
// `changes` sums, because it answers "how many rows did this insert
|
|
383
383
|
// write". `lastInsertRowid` cannot sum, and the dialects do not agree
|
|
384
|
-
// what it means for a multi-row insert
|
|
384
|
+
// what it means for a multi-row insert, so the adapter says which end of
|
|
385
385
|
// the batched run carries its answer, rather than this file knowing.
|
|
386
386
|
const pick =
|
|
387
387
|
getActiveDb().batchInsertIdPosition === 'first'
|
|
@@ -475,8 +475,12 @@ export namespace Mutation {
|
|
|
475
475
|
if (c.operator === '') {
|
|
476
476
|
parts.push(i === 0 ? left : `${c.connector} ${left}`)
|
|
477
477
|
} else {
|
|
478
|
-
|
|
479
|
-
|
|
478
|
+
// `nullComparison` before `evalOperands`, and the order is the point:
|
|
479
|
+
// a null that becomes `IS NULL` must not also bind a parameter.
|
|
480
|
+
const nullOp = nullComparison(c.operator, c.right, c.isRightColumn)
|
|
481
|
+
const clauseStr = nullOp
|
|
482
|
+
? `${left} ${nullOp}`
|
|
483
|
+
: `${left} ${c.operator} ${evalOperands(c.right, params, c.isRightColumn)}`
|
|
480
484
|
parts.push(i === 0 ? clauseStr : `${c.connector} ${clauseStr}`)
|
|
481
485
|
}
|
|
482
486
|
}
|
|
@@ -618,8 +622,8 @@ export namespace Mutation {
|
|
|
618
622
|
for (let i = 0; i < this._clauses.length; i++) {
|
|
619
623
|
const c = this._clauses[i]!
|
|
620
624
|
const left = evalOperands(c.left, params, true)
|
|
621
|
-
// `parseWhereArgs` emits `operator: ''` for the one-argument form
|
|
622
|
-
// `where(DB.raw`…`)` or `where(<subquery>)
|
|
625
|
+
// `parseWhereArgs` emits `operator: ''` for the one-argument form,
|
|
626
|
+
// `where(DB.raw`…`)` or `where(<subquery>)`, where the left operand
|
|
623
627
|
// is the whole condition and there is no right one. This branch is a
|
|
624
628
|
// copy of `UpdateExecutable.evalWhere` above, which is a copy of
|
|
625
629
|
// `formatClause` in query.ts; it was the copy that never got it. The
|
|
@@ -630,8 +634,12 @@ export namespace Mutation {
|
|
|
630
634
|
if (c.operator === '') {
|
|
631
635
|
parts.push(i === 0 ? left : `${c.connector} ${left}`)
|
|
632
636
|
} else {
|
|
633
|
-
|
|
634
|
-
|
|
637
|
+
// `nullComparison` before `evalOperands`, and the order is the point:
|
|
638
|
+
// a null that becomes `IS NULL` must not also bind a parameter.
|
|
639
|
+
const nullOp = nullComparison(c.operator, c.right, c.isRightColumn)
|
|
640
|
+
const clauseStr = nullOp
|
|
641
|
+
? `${left} ${nullOp}`
|
|
642
|
+
: `${left} ${c.operator} ${evalOperands(c.right, params, c.isRightColumn)}`
|
|
635
643
|
parts.push(i === 0 ? clauseStr : `${c.connector} ${clauseStr}`)
|
|
636
644
|
}
|
|
637
645
|
}
|