@remix-run/data-table 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +549 -2
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/lib/adapter.d.ts +550 -0
- package/dist/lib/adapter.d.ts.map +1 -0
- package/dist/lib/adapter.js +1 -0
- package/dist/lib/column.d.ts +193 -0
- package/dist/lib/column.d.ts.map +1 -0
- package/dist/lib/column.js +302 -0
- package/dist/lib/database/execution-context.d.ts +10 -0
- package/dist/lib/database/execution-context.d.ts.map +1 -0
- package/dist/lib/database/execution-context.js +1 -0
- package/dist/lib/database/helpers.d.ts +26 -0
- package/dist/lib/database/helpers.d.ts.map +1 -0
- package/dist/lib/database/helpers.js +116 -0
- package/dist/lib/database/query-execution.d.ts +7 -0
- package/dist/lib/database/query-execution.d.ts.map +1 -0
- package/dist/lib/database/query-execution.js +401 -0
- package/dist/lib/database/relations.d.ts +4 -0
- package/dist/lib/database/relations.d.ts.map +1 -0
- package/dist/lib/database/relations.js +207 -0
- package/dist/lib/database/write-lifecycle.d.ts +13 -0
- package/dist/lib/database/write-lifecycle.d.ts.map +1 -0
- package/dist/lib/database/write-lifecycle.js +279 -0
- package/dist/lib/database.d.ts +264 -0
- package/dist/lib/database.d.ts.map +1 -0
- package/dist/lib/database.js +319 -0
- package/dist/lib/errors.d.ts +59 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +76 -0
- package/dist/lib/inflection.d.ts +3 -0
- package/dist/lib/inflection.d.ts.map +1 -0
- package/dist/lib/inflection.js +56 -0
- package/dist/lib/migrations/filename.d.ts +12 -0
- package/dist/lib/migrations/filename.d.ts.map +1 -0
- package/dist/lib/migrations/filename.js +20 -0
- package/dist/lib/migrations/helpers.d.ts +11 -0
- package/dist/lib/migrations/helpers.d.ts.map +1 -0
- package/dist/lib/migrations/helpers.js +77 -0
- package/dist/lib/migrations/journal-store.d.ts +15 -0
- package/dist/lib/migrations/journal-store.d.ts.map +1 -0
- package/dist/lib/migrations/journal-store.js +83 -0
- package/dist/lib/migrations/registry.d.ts +27 -0
- package/dist/lib/migrations/registry.d.ts.map +1 -0
- package/dist/lib/migrations/registry.js +51 -0
- package/dist/lib/migrations/runner.d.ts +20 -0
- package/dist/lib/migrations/runner.d.ts.map +1 -0
- package/dist/lib/migrations/runner.js +273 -0
- package/dist/lib/migrations/schema-api.d.ts +7 -0
- package/dist/lib/migrations/schema-api.d.ts.map +1 -0
- package/dist/lib/migrations/schema-api.js +326 -0
- package/dist/lib/migrations-node.d.ts +17 -0
- package/dist/lib/migrations-node.d.ts.map +1 -0
- package/dist/lib/migrations-node.js +65 -0
- package/dist/lib/migrations.d.ts +292 -0
- package/dist/lib/migrations.d.ts.map +1 -0
- package/dist/lib/migrations.js +38 -0
- package/dist/lib/operators.d.ts +154 -0
- package/dist/lib/operators.d.ts.map +1 -0
- package/dist/lib/operators.js +218 -0
- package/dist/lib/query.d.ts +159 -0
- package/dist/lib/query.d.ts.map +1 -0
- package/dist/lib/query.js +401 -0
- package/dist/lib/references.d.ts +41 -0
- package/dist/lib/references.d.ts.map +1 -0
- package/dist/lib/references.js +33 -0
- package/dist/lib/sql-helpers.d.ts +50 -0
- package/dist/lib/sql-helpers.d.ts.map +1 -0
- package/dist/lib/sql-helpers.js +111 -0
- package/dist/lib/sql.d.ts +45 -0
- package/dist/lib/sql.d.ts.map +1 -0
- package/dist/lib/sql.js +65 -0
- package/dist/lib/table.d.ts +569 -0
- package/dist/lib/table.d.ts.map +1 -0
- package/dist/lib/table.js +519 -0
- package/dist/lib/types.d.ts +4 -0
- package/dist/lib/types.d.ts.map +1 -0
- package/dist/lib/types.js +1 -0
- package/dist/migrations/node.d.ts +2 -0
- package/dist/migrations/node.d.ts.map +1 -0
- package/dist/migrations/node.js +1 -0
- package/dist/migrations.d.ts +8 -0
- package/dist/migrations.d.ts.map +1 -0
- package/dist/migrations.js +5 -0
- package/dist/operators.d.ts +3 -0
- package/dist/operators.d.ts.map +1 -0
- package/dist/operators.js +1 -0
- package/dist/sql-helpers.d.ts +3 -0
- package/dist/sql-helpers.d.ts.map +1 -0
- package/dist/sql-helpers.js +1 -0
- package/package.json +56 -7
- package/src/index.ts +198 -0
- package/src/lib/adapter.ts +653 -0
- package/src/lib/column.ts +384 -0
- package/src/lib/database/execution-context.ts +15 -0
- package/src/lib/database/helpers.ts +216 -0
- package/src/lib/database/query-execution.ts +638 -0
- package/src/lib/database/relations.ts +332 -0
- package/src/lib/database/write-lifecycle.ts +487 -0
- package/src/lib/database.ts +856 -0
- package/src/lib/errors.ts +119 -0
- package/src/lib/inflection.ts +69 -0
- package/src/lib/migrations/filename.ts +25 -0
- package/src/lib/migrations/helpers.ts +108 -0
- package/src/lib/migrations/journal-store.ts +122 -0
- package/src/lib/migrations/registry.ts +62 -0
- package/src/lib/migrations/runner.ts +374 -0
- package/src/lib/migrations/schema-api.ts +417 -0
- package/src/lib/migrations-node.ts +71 -0
- package/src/lib/migrations.ts +328 -0
- package/src/lib/operators.ts +436 -0
- package/src/lib/query.ts +958 -0
- package/src/lib/references.ts +78 -0
- package/src/lib/sql-helpers.ts +146 -0
- package/src/lib/sql.ts +84 -0
- package/src/lib/table.ts +1309 -0
- package/src/lib/types.ts +3 -0
- package/src/migrations/node.ts +1 -0
- package/src/migrations.ts +26 -0
- package/src/operators.ts +18 -0
- package/src/sql-helpers.ts +9 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CountOperation,
|
|
3
|
+
DataManipulationResult,
|
|
4
|
+
DeleteOperation,
|
|
5
|
+
ExistsOperation,
|
|
6
|
+
InsertManyOperation,
|
|
7
|
+
InsertOperation,
|
|
8
|
+
SelectColumn,
|
|
9
|
+
SelectOperation,
|
|
10
|
+
UpdateOperation,
|
|
11
|
+
UpsertOperation,
|
|
12
|
+
} from '../adapter.ts'
|
|
13
|
+
import { DataTableQueryError } from '../errors.ts'
|
|
14
|
+
import type { ReturningInput, WriteResult, WriteRowResult, WriteRowsResult } from '../database.ts'
|
|
15
|
+
import { normalizeWhereInput } from '../operators.ts'
|
|
16
|
+
import type { AnyQuery, QueryExecutionResult, QueryState } from '../query.ts'
|
|
17
|
+
import { cloneQueryState, querySnapshot } from '../query.ts'
|
|
18
|
+
import type { AnyTable } from '../table.ts'
|
|
19
|
+
import { getPrimaryKeyObject, getTableName } from '../table.ts'
|
|
20
|
+
|
|
21
|
+
import { executeOperation, type QueryExecutionContext } from './execution-context.ts'
|
|
22
|
+
import {
|
|
23
|
+
buildPrimaryKeyPredicate,
|
|
24
|
+
hasScopedWriteModifiers,
|
|
25
|
+
loadPrimaryKeyRowsForScope,
|
|
26
|
+
} from './helpers.ts'
|
|
27
|
+
import { loadRelationsForRows } from './relations.ts'
|
|
28
|
+
import {
|
|
29
|
+
applyAfterReadHooksToLoadedRows,
|
|
30
|
+
applyAfterReadHooksToRows,
|
|
31
|
+
assertReturningCapability,
|
|
32
|
+
normalizeReturningSelection,
|
|
33
|
+
prepareInsertValues,
|
|
34
|
+
prepareUpdateValues,
|
|
35
|
+
runAfterDeleteHook,
|
|
36
|
+
runAfterWriteHook,
|
|
37
|
+
runBeforeDeleteHook,
|
|
38
|
+
} from './write-lifecycle.ts'
|
|
39
|
+
|
|
40
|
+
export async function executeQuery<input extends AnyQuery>(
|
|
41
|
+
database: QueryExecutionContext,
|
|
42
|
+
input: input,
|
|
43
|
+
): Promise<QueryExecutionResult<input>> {
|
|
44
|
+
let snapshot = input[querySnapshot]()
|
|
45
|
+
|
|
46
|
+
switch (snapshot.plan.kind) {
|
|
47
|
+
case 'all':
|
|
48
|
+
return (await executeAll(
|
|
49
|
+
database,
|
|
50
|
+
snapshot.table,
|
|
51
|
+
snapshot.state,
|
|
52
|
+
)) as QueryExecutionResult<input>
|
|
53
|
+
case 'first':
|
|
54
|
+
return (await executeFirst(
|
|
55
|
+
database,
|
|
56
|
+
snapshot.table,
|
|
57
|
+
snapshot.state,
|
|
58
|
+
)) as QueryExecutionResult<input>
|
|
59
|
+
case 'find':
|
|
60
|
+
return (await executeFind(
|
|
61
|
+
database,
|
|
62
|
+
snapshot.table,
|
|
63
|
+
snapshot.state,
|
|
64
|
+
snapshot.plan.value,
|
|
65
|
+
)) as QueryExecutionResult<input>
|
|
66
|
+
case 'count':
|
|
67
|
+
return (await executeCount(
|
|
68
|
+
database,
|
|
69
|
+
snapshot.table,
|
|
70
|
+
snapshot.state,
|
|
71
|
+
)) as QueryExecutionResult<input>
|
|
72
|
+
case 'exists':
|
|
73
|
+
return (await executeExists(
|
|
74
|
+
database,
|
|
75
|
+
snapshot.table,
|
|
76
|
+
snapshot.state,
|
|
77
|
+
)) as QueryExecutionResult<input>
|
|
78
|
+
case 'insert':
|
|
79
|
+
return (await executeInsert(
|
|
80
|
+
database,
|
|
81
|
+
snapshot.table,
|
|
82
|
+
snapshot.plan.values as Record<string, unknown>,
|
|
83
|
+
snapshot.plan.options,
|
|
84
|
+
)) as QueryExecutionResult<input>
|
|
85
|
+
case 'insertMany':
|
|
86
|
+
return (await executeInsertMany(
|
|
87
|
+
database,
|
|
88
|
+
snapshot.table,
|
|
89
|
+
snapshot.plan.values as Record<string, unknown>[],
|
|
90
|
+
snapshot.plan.options,
|
|
91
|
+
)) as QueryExecutionResult<input>
|
|
92
|
+
case 'update':
|
|
93
|
+
return (await executeUpdate(
|
|
94
|
+
database,
|
|
95
|
+
snapshot.table,
|
|
96
|
+
snapshot.state,
|
|
97
|
+
snapshot.plan.changes as Record<string, unknown>,
|
|
98
|
+
snapshot.plan.options,
|
|
99
|
+
)) as QueryExecutionResult<input>
|
|
100
|
+
case 'delete':
|
|
101
|
+
return (await executeDelete(
|
|
102
|
+
database,
|
|
103
|
+
snapshot.table,
|
|
104
|
+
snapshot.state,
|
|
105
|
+
snapshot.plan.options,
|
|
106
|
+
)) as QueryExecutionResult<input>
|
|
107
|
+
case 'upsert':
|
|
108
|
+
return (await executeUpsert(
|
|
109
|
+
database,
|
|
110
|
+
snapshot.table,
|
|
111
|
+
snapshot.plan.values as Record<string, unknown>,
|
|
112
|
+
snapshot.plan.options,
|
|
113
|
+
)) as QueryExecutionResult<input>
|
|
114
|
+
default:
|
|
115
|
+
throw new DataTableQueryError('Unknown query execution mode')
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function loadRowsWithRelationsForQuery(
|
|
120
|
+
database: QueryExecutionContext,
|
|
121
|
+
input: AnyQuery,
|
|
122
|
+
): Promise<Record<string, unknown>[]> {
|
|
123
|
+
let snapshot = input[querySnapshot]()
|
|
124
|
+
return loadRowsWithRelationsForState(database, snapshot.table, snapshot.state)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export async function loadRowsWithRelationsForState(
|
|
128
|
+
database: QueryExecutionContext,
|
|
129
|
+
table: AnyTable,
|
|
130
|
+
state: QueryState,
|
|
131
|
+
): Promise<Record<string, unknown>[]> {
|
|
132
|
+
let operation = createSelectOperation(table, state)
|
|
133
|
+
let result = await database[executeOperation](operation)
|
|
134
|
+
let rows = normalizeRows(result.rows)
|
|
135
|
+
|
|
136
|
+
if (Object.keys(state.with).length === 0) {
|
|
137
|
+
return rows
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return loadRelationsForRows(database, table, rows, state.with)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function executeAll(
|
|
144
|
+
database: QueryExecutionContext,
|
|
145
|
+
table: AnyTable,
|
|
146
|
+
state: QueryState,
|
|
147
|
+
): Promise<Record<string, unknown>[]> {
|
|
148
|
+
let rows = await loadRowsWithRelationsForState(database, table, state)
|
|
149
|
+
return applyAfterReadHooksToLoadedRows(table, rows, state.with)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function executeFirst(
|
|
153
|
+
database: QueryExecutionContext,
|
|
154
|
+
table: AnyTable,
|
|
155
|
+
state: QueryState,
|
|
156
|
+
): Promise<Record<string, unknown> | null> {
|
|
157
|
+
let rows = await executeAll(database, table, {
|
|
158
|
+
...cloneQueryState(state),
|
|
159
|
+
limit: 1,
|
|
160
|
+
})
|
|
161
|
+
|
|
162
|
+
return rows[0] ?? null
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function executeFind(
|
|
166
|
+
database: QueryExecutionContext,
|
|
167
|
+
table: AnyTable,
|
|
168
|
+
state: QueryState,
|
|
169
|
+
value: unknown,
|
|
170
|
+
): Promise<Record<string, unknown> | null> {
|
|
171
|
+
let scopedState = cloneQueryState(state)
|
|
172
|
+
scopedState.where.push(
|
|
173
|
+
normalizeWhereInput(getPrimaryKeyObject(table, value as never) as Record<string, unknown>),
|
|
174
|
+
)
|
|
175
|
+
|
|
176
|
+
return executeFirst(database, table, scopedState)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async function executeCount(
|
|
180
|
+
database: QueryExecutionContext,
|
|
181
|
+
table: AnyTable,
|
|
182
|
+
state: QueryState,
|
|
183
|
+
): Promise<number> {
|
|
184
|
+
let operation: CountOperation<AnyTable> = {
|
|
185
|
+
kind: 'count',
|
|
186
|
+
table,
|
|
187
|
+
joins: [...state.joins],
|
|
188
|
+
where: [...state.where],
|
|
189
|
+
groupBy: [...state.groupBy],
|
|
190
|
+
having: [...state.having],
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
let result = await database[executeOperation](operation)
|
|
194
|
+
|
|
195
|
+
if (result.rows && result.rows[0] && typeof result.rows[0].count === 'number') {
|
|
196
|
+
return result.rows[0].count as number
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (result.rows) {
|
|
200
|
+
return result.rows.length
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return 0
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function executeExists(
|
|
207
|
+
database: QueryExecutionContext,
|
|
208
|
+
table: AnyTable,
|
|
209
|
+
state: QueryState,
|
|
210
|
+
): Promise<boolean> {
|
|
211
|
+
let operation: ExistsOperation<AnyTable> = {
|
|
212
|
+
kind: 'exists',
|
|
213
|
+
table,
|
|
214
|
+
joins: [...state.joins],
|
|
215
|
+
where: [...state.where],
|
|
216
|
+
groupBy: [...state.groupBy],
|
|
217
|
+
having: [...state.having],
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let result = await database[executeOperation](operation)
|
|
221
|
+
|
|
222
|
+
if (result.rows && result.rows[0] && typeof result.rows[0].exists === 'boolean') {
|
|
223
|
+
return result.rows[0].exists as boolean
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (result.rows && result.rows[0] && typeof result.rows[0].count === 'number') {
|
|
227
|
+
return Number(result.rows[0].count) > 0
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return Boolean(result.rows && result.rows.length > 0)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async function executeInsert(
|
|
234
|
+
database: QueryExecutionContext,
|
|
235
|
+
table: AnyTable,
|
|
236
|
+
values: Record<string, unknown>,
|
|
237
|
+
options?: { returning?: ReturningInput<Record<string, unknown>>; touch?: boolean },
|
|
238
|
+
): Promise<WriteResult | WriteRowResult<Record<string, unknown>>> {
|
|
239
|
+
let preparedValues = prepareInsertValues(
|
|
240
|
+
table,
|
|
241
|
+
values as never,
|
|
242
|
+
database.now(),
|
|
243
|
+
options?.touch ?? true,
|
|
244
|
+
)
|
|
245
|
+
let returning = options?.returning
|
|
246
|
+
|
|
247
|
+
assertReturningCapability(database.adapter, 'insert', returning)
|
|
248
|
+
|
|
249
|
+
if (returning) {
|
|
250
|
+
let operation: InsertOperation<AnyTable> = {
|
|
251
|
+
kind: 'insert',
|
|
252
|
+
table,
|
|
253
|
+
values: preparedValues,
|
|
254
|
+
returning: normalizeReturningSelection(returning),
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
let result = await database[executeOperation](operation)
|
|
258
|
+
let row = applyAfterReadHooksToRows(table, normalizeRows(result.rows))[0] ?? null
|
|
259
|
+
let affectedRows = result.affectedRows ?? 0
|
|
260
|
+
runAfterWriteHook(table, {
|
|
261
|
+
operation: 'create',
|
|
262
|
+
tableName: getTableName(table),
|
|
263
|
+
values: [preparedValues],
|
|
264
|
+
affectedRows,
|
|
265
|
+
insertId: result.insertId,
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
affectedRows,
|
|
270
|
+
insertId: result.insertId,
|
|
271
|
+
row,
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
let operation: InsertOperation<AnyTable> = {
|
|
276
|
+
kind: 'insert',
|
|
277
|
+
table,
|
|
278
|
+
values: preparedValues,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let result = await database[executeOperation](operation)
|
|
282
|
+
let affectedRows = result.affectedRows ?? 0
|
|
283
|
+
runAfterWriteHook(table, {
|
|
284
|
+
operation: 'create',
|
|
285
|
+
tableName: getTableName(table),
|
|
286
|
+
values: [preparedValues],
|
|
287
|
+
affectedRows,
|
|
288
|
+
insertId: result.insertId,
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
affectedRows,
|
|
293
|
+
insertId: result.insertId,
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function executeInsertMany(
|
|
298
|
+
database: QueryExecutionContext,
|
|
299
|
+
table: AnyTable,
|
|
300
|
+
values: Record<string, unknown>[],
|
|
301
|
+
options?: { returning?: ReturningInput<Record<string, unknown>>; touch?: boolean },
|
|
302
|
+
): Promise<WriteResult | WriteRowsResult<Record<string, unknown>>> {
|
|
303
|
+
let preparedValues = values.map((value) =>
|
|
304
|
+
prepareInsertValues(table, value as never, database.now(), options?.touch ?? true),
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
if (
|
|
308
|
+
preparedValues.length > 0 &&
|
|
309
|
+
preparedValues.every((preparedValue) => Object.keys(preparedValue).length === 0)
|
|
310
|
+
) {
|
|
311
|
+
throw new DataTableQueryError(
|
|
312
|
+
'insertMany() requires at least one explicit value across the batch',
|
|
313
|
+
)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
let returning = options?.returning
|
|
317
|
+
assertReturningCapability(database.adapter, 'insertMany', returning)
|
|
318
|
+
|
|
319
|
+
if (returning) {
|
|
320
|
+
let operation: InsertManyOperation<AnyTable> = {
|
|
321
|
+
kind: 'insertMany',
|
|
322
|
+
table,
|
|
323
|
+
values: preparedValues,
|
|
324
|
+
returning: normalizeReturningSelection(returning),
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
let result = await database[executeOperation](operation)
|
|
328
|
+
let affectedRows = result.affectedRows ?? 0
|
|
329
|
+
runAfterWriteHook(table, {
|
|
330
|
+
operation: 'create',
|
|
331
|
+
tableName: getTableName(table),
|
|
332
|
+
values: preparedValues,
|
|
333
|
+
affectedRows,
|
|
334
|
+
insertId: result.insertId,
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
return {
|
|
338
|
+
affectedRows,
|
|
339
|
+
insertId: result.insertId,
|
|
340
|
+
rows: applyAfterReadHooksToRows(table, normalizeRows(result.rows)),
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
let operation: InsertManyOperation<AnyTable> = {
|
|
345
|
+
kind: 'insertMany',
|
|
346
|
+
table,
|
|
347
|
+
values: preparedValues,
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
let result = await database[executeOperation](operation)
|
|
351
|
+
let affectedRows = result.affectedRows ?? 0
|
|
352
|
+
runAfterWriteHook(table, {
|
|
353
|
+
operation: 'create',
|
|
354
|
+
tableName: getTableName(table),
|
|
355
|
+
values: preparedValues,
|
|
356
|
+
affectedRows,
|
|
357
|
+
insertId: result.insertId,
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
affectedRows,
|
|
362
|
+
insertId: result.insertId,
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async function executeUpdate(
|
|
367
|
+
database: QueryExecutionContext,
|
|
368
|
+
table: AnyTable,
|
|
369
|
+
state: QueryState,
|
|
370
|
+
changes: Record<string, unknown>,
|
|
371
|
+
options?: { returning?: ReturningInput<Record<string, unknown>>; touch?: boolean },
|
|
372
|
+
): Promise<WriteResult | WriteRowsResult<Record<string, unknown>>> {
|
|
373
|
+
let returning = options?.returning
|
|
374
|
+
assertReturningCapability(database.adapter, 'update', returning)
|
|
375
|
+
let preparedChanges = prepareUpdateValues(
|
|
376
|
+
table,
|
|
377
|
+
changes as never,
|
|
378
|
+
database.now(),
|
|
379
|
+
options?.touch ?? true,
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
if (Object.keys(preparedChanges).length === 0) {
|
|
383
|
+
throw new DataTableQueryError('update() requires at least one change')
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
let result: DataManipulationResult
|
|
387
|
+
|
|
388
|
+
if (hasScopedWriteModifiers(state)) {
|
|
389
|
+
result = await database.transaction(async (tx) => {
|
|
390
|
+
let primaryKeys = await loadPrimaryKeyRowsForScope(tx, table, state)
|
|
391
|
+
let primaryKeyPredicate = buildPrimaryKeyPredicate(table, primaryKeys)
|
|
392
|
+
|
|
393
|
+
if (!primaryKeyPredicate) {
|
|
394
|
+
return {
|
|
395
|
+
affectedRows: 0,
|
|
396
|
+
insertId: undefined,
|
|
397
|
+
rows: returning ? [] : undefined,
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
return tx[executeOperation]({
|
|
402
|
+
kind: 'update',
|
|
403
|
+
table,
|
|
404
|
+
changes: preparedChanges,
|
|
405
|
+
where: [primaryKeyPredicate],
|
|
406
|
+
returning: returning ? normalizeReturningSelection(returning) : undefined,
|
|
407
|
+
})
|
|
408
|
+
})
|
|
409
|
+
} else {
|
|
410
|
+
let operation: UpdateOperation<AnyTable> = {
|
|
411
|
+
kind: 'update',
|
|
412
|
+
table,
|
|
413
|
+
changes: preparedChanges,
|
|
414
|
+
where: [...state.where],
|
|
415
|
+
returning: returning ? normalizeReturningSelection(returning) : undefined,
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
result = await database[executeOperation](operation)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
let affectedRows = result.affectedRows ?? 0
|
|
422
|
+
runAfterWriteHook(table, {
|
|
423
|
+
operation: 'update',
|
|
424
|
+
tableName: getTableName(table),
|
|
425
|
+
values: [preparedChanges],
|
|
426
|
+
affectedRows,
|
|
427
|
+
insertId: result.insertId,
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
if (!returning) {
|
|
431
|
+
return {
|
|
432
|
+
affectedRows,
|
|
433
|
+
insertId: result.insertId,
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
return {
|
|
438
|
+
affectedRows,
|
|
439
|
+
insertId: result.insertId,
|
|
440
|
+
rows: applyAfterReadHooksToRows(table, normalizeRows(result.rows)),
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
async function executeDelete(
|
|
445
|
+
database: QueryExecutionContext,
|
|
446
|
+
table: AnyTable,
|
|
447
|
+
state: QueryState,
|
|
448
|
+
options?: { returning?: ReturningInput<Record<string, unknown>> },
|
|
449
|
+
): Promise<WriteResult | WriteRowsResult<Record<string, unknown>>> {
|
|
450
|
+
let returning = options?.returning
|
|
451
|
+
assertReturningCapability(database.adapter, 'delete', returning)
|
|
452
|
+
let tableName = getTableName(table)
|
|
453
|
+
let deleteContext = {
|
|
454
|
+
tableName,
|
|
455
|
+
where: [...state.where],
|
|
456
|
+
orderBy: [...state.orderBy],
|
|
457
|
+
limit: state.limit,
|
|
458
|
+
offset: state.offset,
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
runBeforeDeleteHook(table, deleteContext)
|
|
462
|
+
let result: DataManipulationResult
|
|
463
|
+
|
|
464
|
+
if (hasScopedWriteModifiers(state)) {
|
|
465
|
+
result = await database.transaction(async (tx) => {
|
|
466
|
+
let primaryKeys = await loadPrimaryKeyRowsForScope(tx, table, state)
|
|
467
|
+
let primaryKeyPredicate = buildPrimaryKeyPredicate(table, primaryKeys)
|
|
468
|
+
|
|
469
|
+
if (!primaryKeyPredicate) {
|
|
470
|
+
return {
|
|
471
|
+
affectedRows: 0,
|
|
472
|
+
insertId: undefined,
|
|
473
|
+
rows: returning ? [] : undefined,
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
return tx[executeOperation]({
|
|
478
|
+
kind: 'delete',
|
|
479
|
+
table,
|
|
480
|
+
where: [primaryKeyPredicate],
|
|
481
|
+
returning: returning ? normalizeReturningSelection(returning) : undefined,
|
|
482
|
+
})
|
|
483
|
+
})
|
|
484
|
+
} else {
|
|
485
|
+
let operation: DeleteOperation<AnyTable> = {
|
|
486
|
+
kind: 'delete',
|
|
487
|
+
table,
|
|
488
|
+
where: [...state.where],
|
|
489
|
+
returning: returning ? normalizeReturningSelection(returning) : undefined,
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
result = await database[executeOperation](operation)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
let affectedRows = result.affectedRows ?? 0
|
|
496
|
+
runAfterDeleteHook(table, {
|
|
497
|
+
tableName,
|
|
498
|
+
where: deleteContext.where,
|
|
499
|
+
orderBy: deleteContext.orderBy,
|
|
500
|
+
limit: deleteContext.limit,
|
|
501
|
+
offset: deleteContext.offset,
|
|
502
|
+
affectedRows,
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
if (!returning) {
|
|
506
|
+
return {
|
|
507
|
+
affectedRows,
|
|
508
|
+
insertId: result.insertId,
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
affectedRows,
|
|
514
|
+
insertId: result.insertId,
|
|
515
|
+
rows: applyAfterReadHooksToRows(table, normalizeRows(result.rows)),
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
async function executeUpsert(
|
|
520
|
+
database: QueryExecutionContext,
|
|
521
|
+
table: AnyTable,
|
|
522
|
+
values: Record<string, unknown>,
|
|
523
|
+
options?: {
|
|
524
|
+
returning?: ReturningInput<Record<string, unknown>>
|
|
525
|
+
touch?: boolean
|
|
526
|
+
conflictTarget?: string[]
|
|
527
|
+
update?: Record<string, unknown>
|
|
528
|
+
},
|
|
529
|
+
): Promise<WriteResult | WriteRowResult<Record<string, unknown>>> {
|
|
530
|
+
if (!database.adapter.capabilities.upsert) {
|
|
531
|
+
throw new DataTableQueryError('Adapter does not support upsert')
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
let preparedValues = prepareInsertValues(
|
|
535
|
+
table,
|
|
536
|
+
values as never,
|
|
537
|
+
database.now(),
|
|
538
|
+
options?.touch ?? true,
|
|
539
|
+
)
|
|
540
|
+
let updateChanges = options?.update
|
|
541
|
+
? prepareUpdateValues(
|
|
542
|
+
table,
|
|
543
|
+
options.update as never,
|
|
544
|
+
database.now(),
|
|
545
|
+
options?.touch ?? true,
|
|
546
|
+
'create',
|
|
547
|
+
)
|
|
548
|
+
: undefined
|
|
549
|
+
let returning = options?.returning
|
|
550
|
+
assertReturningCapability(database.adapter, 'upsert', returning)
|
|
551
|
+
|
|
552
|
+
if (returning) {
|
|
553
|
+
let operation: UpsertOperation<AnyTable> = {
|
|
554
|
+
kind: 'upsert',
|
|
555
|
+
table,
|
|
556
|
+
values: preparedValues,
|
|
557
|
+
conflictTarget: options?.conflictTarget ? [...options.conflictTarget] : undefined,
|
|
558
|
+
update: updateChanges,
|
|
559
|
+
returning: normalizeReturningSelection(returning),
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
let result = await database[executeOperation](operation)
|
|
563
|
+
let row = applyAfterReadHooksToRows(table, normalizeRows(result.rows))[0] ?? null
|
|
564
|
+
let affectedRows = result.affectedRows ?? 0
|
|
565
|
+
let preparedWriteValues = updateChanges ? [preparedValues, updateChanges] : [preparedValues]
|
|
566
|
+
runAfterWriteHook(table, {
|
|
567
|
+
operation: 'create',
|
|
568
|
+
tableName: getTableName(table),
|
|
569
|
+
values: preparedWriteValues,
|
|
570
|
+
affectedRows,
|
|
571
|
+
insertId: result.insertId,
|
|
572
|
+
})
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
affectedRows,
|
|
576
|
+
insertId: result.insertId,
|
|
577
|
+
row,
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
let operation: UpsertOperation<AnyTable> = {
|
|
582
|
+
kind: 'upsert',
|
|
583
|
+
table,
|
|
584
|
+
values: preparedValues,
|
|
585
|
+
conflictTarget: options?.conflictTarget ? [...options.conflictTarget] : undefined,
|
|
586
|
+
update: updateChanges,
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
let result = await database[executeOperation](operation)
|
|
590
|
+
let affectedRows = result.affectedRows ?? 0
|
|
591
|
+
let preparedWriteValues = updateChanges ? [preparedValues, updateChanges] : [preparedValues]
|
|
592
|
+
runAfterWriteHook(table, {
|
|
593
|
+
operation: 'create',
|
|
594
|
+
tableName: getTableName(table),
|
|
595
|
+
values: preparedWriteValues,
|
|
596
|
+
affectedRows,
|
|
597
|
+
insertId: result.insertId,
|
|
598
|
+
})
|
|
599
|
+
|
|
600
|
+
return {
|
|
601
|
+
affectedRows,
|
|
602
|
+
insertId: result.insertId,
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
function createSelectOperation(table: AnyTable, state: QueryState): SelectOperation<AnyTable> {
|
|
607
|
+
let clonedState = cloneQueryState(state)
|
|
608
|
+
|
|
609
|
+
return {
|
|
610
|
+
kind: 'select',
|
|
611
|
+
table,
|
|
612
|
+
select: cloneSelection(clonedState.select),
|
|
613
|
+
distinct: clonedState.distinct,
|
|
614
|
+
joins: clonedState.joins,
|
|
615
|
+
where: clonedState.where,
|
|
616
|
+
groupBy: clonedState.groupBy,
|
|
617
|
+
having: clonedState.having,
|
|
618
|
+
orderBy: clonedState.orderBy,
|
|
619
|
+
limit: clonedState.limit,
|
|
620
|
+
offset: clonedState.offset,
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
function cloneSelection(selection: '*' | SelectColumn[]): '*' | SelectColumn[] {
|
|
625
|
+
if (selection === '*') {
|
|
626
|
+
return '*'
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return selection.map((column) => ({ ...column }))
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function normalizeRows(rows: DataManipulationResult['rows']): Record<string, unknown>[] {
|
|
633
|
+
if (!rows) {
|
|
634
|
+
return []
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
return rows.map((row) => ({ ...row }))
|
|
638
|
+
}
|