@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
package/src/lib/query.ts
ADDED
|
@@ -0,0 +1,958 @@
|
|
|
1
|
+
import type { JoinClause, JoinType, SelectColumn } from './adapter.ts'
|
|
2
|
+
import { DataTableQueryError, DataTableValidationError } from './errors.ts'
|
|
3
|
+
import type {
|
|
4
|
+
MergeColumnTypeMaps,
|
|
5
|
+
PrimaryKeyInputForRow,
|
|
6
|
+
QueryColumnInput,
|
|
7
|
+
QueryColumnName,
|
|
8
|
+
QueryColumnTypeMap,
|
|
9
|
+
QueryColumnTypeMapFromRow,
|
|
10
|
+
QueryColumns,
|
|
11
|
+
QueryTableInput,
|
|
12
|
+
RelationMapForSourceName,
|
|
13
|
+
ReturningInput,
|
|
14
|
+
SelectedAliasRow,
|
|
15
|
+
WriteResult,
|
|
16
|
+
WriteRowResult,
|
|
17
|
+
WriteRowsResult,
|
|
18
|
+
} from './database.ts'
|
|
19
|
+
import type { Predicate, WhereInput } from './operators.ts'
|
|
20
|
+
import { normalizeWhereInput } from './operators.ts'
|
|
21
|
+
import { normalizeColumnInput } from './references.ts'
|
|
22
|
+
import type { AnyRelation, AnyTable, LoadedRelationMap, OrderByClause } from './table.ts'
|
|
23
|
+
import { getTableColumns, getTableName } from './table.ts'
|
|
24
|
+
|
|
25
|
+
type QueryBindingState = 'bound' | 'unbound'
|
|
26
|
+
|
|
27
|
+
type InsertQueryOptions<row extends Record<string, unknown>> = {
|
|
28
|
+
returning?: ReturningInput<row>
|
|
29
|
+
touch?: boolean
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
type DeleteQueryOptions<row extends Record<string, unknown>> = {
|
|
33
|
+
returning?: ReturningInput<row>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type UpsertQueryOptions<row extends Record<string, unknown>> = {
|
|
37
|
+
returning?: ReturningInput<row>
|
|
38
|
+
touch?: boolean
|
|
39
|
+
conflictTarget?: (keyof row & string)[]
|
|
40
|
+
update?: Partial<row>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type QueryState = {
|
|
44
|
+
select: '*' | SelectColumn[]
|
|
45
|
+
distinct: boolean
|
|
46
|
+
joins: JoinClause[]
|
|
47
|
+
where: Predicate<string>[]
|
|
48
|
+
groupBy: string[]
|
|
49
|
+
having: Predicate<string>[]
|
|
50
|
+
orderBy: OrderByClause[]
|
|
51
|
+
limit?: number
|
|
52
|
+
offset?: number
|
|
53
|
+
with: Record<string, AnyRelation>
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
type QueryPlanMap<row extends Record<string, unknown>, primaryKey extends readonly string[]> = {
|
|
57
|
+
all: { kind: 'all' }
|
|
58
|
+
first: { kind: 'first' }
|
|
59
|
+
find: {
|
|
60
|
+
kind: 'find'
|
|
61
|
+
value: PrimaryKeyInputForRow<row, primaryKey>
|
|
62
|
+
}
|
|
63
|
+
count: { kind: 'count' }
|
|
64
|
+
exists: { kind: 'exists' }
|
|
65
|
+
insert: {
|
|
66
|
+
kind: 'insert'
|
|
67
|
+
values: Partial<row>
|
|
68
|
+
options?: InsertQueryOptions<row>
|
|
69
|
+
}
|
|
70
|
+
insertMany: {
|
|
71
|
+
kind: 'insertMany'
|
|
72
|
+
values: Partial<row>[]
|
|
73
|
+
options?: InsertQueryOptions<row>
|
|
74
|
+
}
|
|
75
|
+
update: {
|
|
76
|
+
kind: 'update'
|
|
77
|
+
changes: Partial<row>
|
|
78
|
+
options?: InsertQueryOptions<row>
|
|
79
|
+
}
|
|
80
|
+
delete: {
|
|
81
|
+
kind: 'delete'
|
|
82
|
+
options?: DeleteQueryOptions<row>
|
|
83
|
+
}
|
|
84
|
+
upsert: {
|
|
85
|
+
kind: 'upsert'
|
|
86
|
+
values: Partial<row>
|
|
87
|
+
options?: UpsertQueryOptions<row>
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type QueryExecutionMode = keyof QueryPlanMap<Record<string, unknown>, readonly string[]>
|
|
92
|
+
|
|
93
|
+
type QueryPhase<
|
|
94
|
+
binding extends QueryBindingState = QueryBindingState,
|
|
95
|
+
mode extends QueryExecutionMode = QueryExecutionMode,
|
|
96
|
+
> = {
|
|
97
|
+
binding: binding
|
|
98
|
+
mode: mode
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export type BoundQueryPhase<mode extends QueryExecutionMode = QueryExecutionMode> = QueryPhase<
|
|
102
|
+
'bound',
|
|
103
|
+
mode
|
|
104
|
+
>
|
|
105
|
+
|
|
106
|
+
export type UnboundQueryPhase<mode extends QueryExecutionMode = QueryExecutionMode> = QueryPhase<
|
|
107
|
+
'unbound',
|
|
108
|
+
mode
|
|
109
|
+
>
|
|
110
|
+
|
|
111
|
+
type AnyQuerySource = QueryTableInput<string, Record<string, unknown>, readonly string[]>
|
|
112
|
+
|
|
113
|
+
type QuerySourceTableName<source extends AnyQuerySource> =
|
|
114
|
+
source extends QueryTableInput<infer tableName, any, any> ? tableName : never
|
|
115
|
+
|
|
116
|
+
type QuerySourceRow<source extends AnyQuerySource> =
|
|
117
|
+
source extends QueryTableInput<any, infer row, any> ? row : never
|
|
118
|
+
|
|
119
|
+
type QuerySourcePrimaryKey<source extends AnyQuerySource> =
|
|
120
|
+
source extends QueryTableInput<any, any, infer primaryKey> ? primaryKey : never
|
|
121
|
+
|
|
122
|
+
type QuerySourceColumnTypes<source extends AnyQuerySource> = QueryColumnTypeMapFromRow<
|
|
123
|
+
QuerySourceTableName<source>,
|
|
124
|
+
QuerySourceRow<source>
|
|
125
|
+
>
|
|
126
|
+
|
|
127
|
+
type QueryPlan<
|
|
128
|
+
row extends Record<string, unknown>,
|
|
129
|
+
primaryKey extends readonly string[],
|
|
130
|
+
mode extends QueryExecutionMode = QueryExecutionMode,
|
|
131
|
+
> = QueryPlanMap<row, primaryKey>[mode]
|
|
132
|
+
|
|
133
|
+
type QueryResultMap<row extends Record<string, unknown>, loaded extends Record<string, unknown>> = {
|
|
134
|
+
all: Array<row & loaded>
|
|
135
|
+
first: (row & loaded) | null
|
|
136
|
+
find: (row & loaded) | null
|
|
137
|
+
count: number
|
|
138
|
+
exists: boolean
|
|
139
|
+
insert: WriteResult | WriteRowResult<row>
|
|
140
|
+
insertMany: WriteResult | WriteRowsResult<row>
|
|
141
|
+
update: WriteResult | WriteRowsResult<row>
|
|
142
|
+
delete: WriteResult | WriteRowsResult<row>
|
|
143
|
+
upsert: WriteResult | WriteRowResult<row>
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export type AnyQuery = Query<any, any, any, any, any>
|
|
147
|
+
|
|
148
|
+
type QuerySource<input extends AnyQuery> =
|
|
149
|
+
input extends Query<infer source, any, any, any, any> ? source : never
|
|
150
|
+
|
|
151
|
+
type QueryColumnTypes<input extends AnyQuery> =
|
|
152
|
+
input extends Query<any, infer columnTypes, any, any, any> ? columnTypes : never
|
|
153
|
+
|
|
154
|
+
type QueryRow<input extends AnyQuery> =
|
|
155
|
+
input extends Query<any, any, infer row, any, any> ? row : never
|
|
156
|
+
|
|
157
|
+
type QueryLoaded<input extends AnyQuery> =
|
|
158
|
+
input extends Query<any, any, any, infer loaded, any> ? loaded : never
|
|
159
|
+
|
|
160
|
+
type QueryPhaseOf<input extends AnyQuery> =
|
|
161
|
+
input extends Query<any, any, any, any, infer phase> ? phase : never
|
|
162
|
+
|
|
163
|
+
type QueryBinding<input extends AnyQuery> = QueryPhaseOf<input>['binding']
|
|
164
|
+
|
|
165
|
+
type QueryMode<input extends AnyQuery> = QueryPhaseOf<input>['mode']
|
|
166
|
+
|
|
167
|
+
type QueryPhaseBinding<phase extends QueryPhase> = phase['binding']
|
|
168
|
+
|
|
169
|
+
type QueryPhaseMode<phase extends QueryPhase> = phase['mode']
|
|
170
|
+
|
|
171
|
+
type QueryAllPhase<phase extends QueryPhase> = QueryPhase<QueryPhaseBinding<phase>, 'all'>
|
|
172
|
+
|
|
173
|
+
type QueryNextPhase<phase extends QueryPhase, mode extends QueryExecutionMode> = QueryPhase<
|
|
174
|
+
QueryPhaseBinding<phase>,
|
|
175
|
+
mode
|
|
176
|
+
>
|
|
177
|
+
|
|
178
|
+
type QueryWith<input extends AnyQuery, phase extends QueryPhase> = Query<
|
|
179
|
+
QuerySource<input>,
|
|
180
|
+
QueryColumnTypes<input>,
|
|
181
|
+
QueryRow<input>,
|
|
182
|
+
QueryLoaded<input>,
|
|
183
|
+
phase
|
|
184
|
+
>
|
|
185
|
+
|
|
186
|
+
type QueryTerminalResult<input extends AnyQuery, mode extends QueryExecutionMode, result> =
|
|
187
|
+
QueryBinding<input> extends 'bound' ? Promise<result> : QueryWith<input, UnboundQueryPhase<mode>>
|
|
188
|
+
|
|
189
|
+
export type QueryExecutionResult<input> = input extends AnyQuery
|
|
190
|
+
? QueryResultMap<QueryRow<input>, QueryLoaded<input>>[Extract<
|
|
191
|
+
QueryMode<input>,
|
|
192
|
+
QueryExecutionMode
|
|
193
|
+
>]
|
|
194
|
+
: never
|
|
195
|
+
|
|
196
|
+
type QueryRuntime = {
|
|
197
|
+
exec<input extends AnyQuery>(input: input): Promise<QueryExecutionResult<input>>
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
type QuerySnapshot<
|
|
201
|
+
source extends AnyQuerySource = AnyQuerySource,
|
|
202
|
+
row extends Record<string, unknown> = Record<string, unknown>,
|
|
203
|
+
mode extends QueryExecutionMode = QueryExecutionMode,
|
|
204
|
+
> = {
|
|
205
|
+
table: source
|
|
206
|
+
state: QueryState
|
|
207
|
+
plan: QueryPlan<row, QuerySourcePrimaryKey<source>, mode>
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export const bindQueryRuntime = Symbol('bindQueryRuntime')
|
|
211
|
+
export const querySnapshot = Symbol('querySnapshot')
|
|
212
|
+
|
|
213
|
+
declare const queryTypeBrand: unique symbol
|
|
214
|
+
|
|
215
|
+
export class Query<
|
|
216
|
+
source extends AnyQuerySource,
|
|
217
|
+
columnTypes extends Record<string, unknown> = QuerySourceColumnTypes<source>,
|
|
218
|
+
row extends Record<string, unknown> = QuerySourceRow<source>,
|
|
219
|
+
loaded extends Record<string, unknown> = {},
|
|
220
|
+
phase extends QueryPhase = UnboundQueryPhase<'all'>,
|
|
221
|
+
> {
|
|
222
|
+
declare readonly [queryTypeBrand]: {
|
|
223
|
+
binding: QueryPhaseBinding<phase>
|
|
224
|
+
mode: QueryPhaseMode<phase>
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
#table: source
|
|
228
|
+
#state: QueryState
|
|
229
|
+
#plan: QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>
|
|
230
|
+
#runtime?: QueryRuntime
|
|
231
|
+
|
|
232
|
+
constructor(table: source) {
|
|
233
|
+
this.#table = table
|
|
234
|
+
this.#state = createInitialQueryState()
|
|
235
|
+
this.#plan = { kind: 'all' } as QueryPlan<
|
|
236
|
+
row,
|
|
237
|
+
QuerySourcePrimaryKey<source>,
|
|
238
|
+
QueryPhaseMode<phase>
|
|
239
|
+
>
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
static #createInternal<
|
|
243
|
+
source extends AnyQuerySource,
|
|
244
|
+
columnTypes extends Record<string, unknown>,
|
|
245
|
+
row extends Record<string, unknown>,
|
|
246
|
+
loaded extends Record<string, unknown>,
|
|
247
|
+
phase extends QueryPhase,
|
|
248
|
+
>(
|
|
249
|
+
table: source,
|
|
250
|
+
state: QueryState,
|
|
251
|
+
plan: QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>,
|
|
252
|
+
runtime?: QueryRuntime,
|
|
253
|
+
): Query<source, columnTypes, row, loaded, phase> {
|
|
254
|
+
let output = new Query(table) as Query<source, columnTypes, row, loaded, phase>
|
|
255
|
+
|
|
256
|
+
output.#state = cloneQueryState(state)
|
|
257
|
+
output.#plan = cloneQueryPlan(
|
|
258
|
+
plan as QueryPlan<row, QuerySourcePrimaryKey<source>>,
|
|
259
|
+
) as QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>
|
|
260
|
+
output.#runtime = runtime
|
|
261
|
+
|
|
262
|
+
return output
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
select<selection extends (keyof row & string)[]>(
|
|
266
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
267
|
+
...columns: selection
|
|
268
|
+
): Query<source, columnTypes, Pick<row, selection[number]>, loaded, QueryAllPhase<phase>>
|
|
269
|
+
select<selection extends Record<string, QueryColumnInput<columnTypes>>>(
|
|
270
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
271
|
+
selection: selection,
|
|
272
|
+
): Query<
|
|
273
|
+
source,
|
|
274
|
+
columnTypes,
|
|
275
|
+
SelectedAliasRow<columnTypes, selection>,
|
|
276
|
+
loaded,
|
|
277
|
+
QueryAllPhase<phase>
|
|
278
|
+
>
|
|
279
|
+
select(
|
|
280
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
281
|
+
...input: [Record<string, QueryColumnInput<columnTypes>>] | (keyof row & string)[]
|
|
282
|
+
): Query<source, columnTypes, any, loaded, QueryAllPhase<phase>> {
|
|
283
|
+
if (
|
|
284
|
+
input.length === 1 &&
|
|
285
|
+
typeof input[0] === 'object' &&
|
|
286
|
+
input[0] !== null &&
|
|
287
|
+
!Array.isArray(input[0])
|
|
288
|
+
) {
|
|
289
|
+
let selection = input[0] as Record<string, QueryColumnInput<columnTypes>>
|
|
290
|
+
let aliases = Object.keys(selection)
|
|
291
|
+
let select = aliases.map((alias) => ({
|
|
292
|
+
column: normalizeColumnInput(selection[alias]),
|
|
293
|
+
alias,
|
|
294
|
+
}))
|
|
295
|
+
|
|
296
|
+
return this.#clone({ select }) as Query<
|
|
297
|
+
source,
|
|
298
|
+
columnTypes,
|
|
299
|
+
any,
|
|
300
|
+
loaded,
|
|
301
|
+
QueryAllPhase<phase>
|
|
302
|
+
>
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
let columns = input as (keyof row & string)[]
|
|
306
|
+
|
|
307
|
+
return this.#clone({
|
|
308
|
+
select: columns.map((column) => ({ column, alias: column })),
|
|
309
|
+
}) as Query<source, columnTypes, any, loaded, QueryAllPhase<phase>>
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
distinct(
|
|
313
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
314
|
+
value = true,
|
|
315
|
+
): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
|
|
316
|
+
return this.#clone({ distinct: value })
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
where(
|
|
320
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
321
|
+
input: WhereInput<QueryColumns<columnTypes>>,
|
|
322
|
+
): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
|
|
323
|
+
let predicate = normalizeWhereInput(input)
|
|
324
|
+
let normalizedPredicate = normalizePredicateValues(
|
|
325
|
+
predicate,
|
|
326
|
+
createPredicateColumnResolver([this.#table, ...this.#state.joins.map((join) => join.table)]),
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
return this.#clone({
|
|
330
|
+
where: [...this.#state.where, normalizedPredicate],
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
having(
|
|
335
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
336
|
+
input: WhereInput<QueryColumns<columnTypes>>,
|
|
337
|
+
): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
|
|
338
|
+
let predicate = normalizeWhereInput(input)
|
|
339
|
+
let normalizedPredicate = normalizePredicateValues(
|
|
340
|
+
predicate,
|
|
341
|
+
createPredicateColumnResolver([this.#table, ...this.#state.joins.map((join) => join.table)]),
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
return this.#clone({
|
|
345
|
+
having: [...this.#state.having, normalizedPredicate],
|
|
346
|
+
})
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
join<target extends AnyTable>(
|
|
350
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
351
|
+
target: target,
|
|
352
|
+
on: Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>,
|
|
353
|
+
type: JoinType = 'inner',
|
|
354
|
+
): Query<
|
|
355
|
+
source,
|
|
356
|
+
MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
|
|
357
|
+
row,
|
|
358
|
+
loaded,
|
|
359
|
+
QueryAllPhase<phase>
|
|
360
|
+
> {
|
|
361
|
+
let normalizedOn = normalizePredicateValues(
|
|
362
|
+
on,
|
|
363
|
+
createPredicateColumnResolver([
|
|
364
|
+
this.#table,
|
|
365
|
+
...this.#state.joins.map((join) => join.table),
|
|
366
|
+
target,
|
|
367
|
+
]),
|
|
368
|
+
) as Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>
|
|
369
|
+
|
|
370
|
+
return this.#clone({
|
|
371
|
+
joins: [...this.#state.joins, { type, table: target, on: normalizedOn }],
|
|
372
|
+
}) as Query<
|
|
373
|
+
source,
|
|
374
|
+
MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
|
|
375
|
+
row,
|
|
376
|
+
loaded,
|
|
377
|
+
QueryAllPhase<phase>
|
|
378
|
+
>
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
leftJoin<target extends AnyTable>(
|
|
382
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
383
|
+
target: target,
|
|
384
|
+
on: Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>,
|
|
385
|
+
): Query<
|
|
386
|
+
source,
|
|
387
|
+
MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
|
|
388
|
+
row,
|
|
389
|
+
loaded,
|
|
390
|
+
QueryAllPhase<phase>
|
|
391
|
+
> {
|
|
392
|
+
return this.join(target, on, 'left')
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
rightJoin<target extends AnyTable>(
|
|
396
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
397
|
+
target: target,
|
|
398
|
+
on: Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>,
|
|
399
|
+
): Query<
|
|
400
|
+
source,
|
|
401
|
+
MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
|
|
402
|
+
row,
|
|
403
|
+
loaded,
|
|
404
|
+
QueryAllPhase<phase>
|
|
405
|
+
> {
|
|
406
|
+
return this.join(target, on, 'right')
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
orderBy(
|
|
410
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
411
|
+
column: QueryColumnInput<columnTypes>,
|
|
412
|
+
direction: 'asc' | 'desc' = 'asc',
|
|
413
|
+
): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
|
|
414
|
+
return this.#clone({
|
|
415
|
+
orderBy: [...this.#state.orderBy, { column: normalizeColumnInput(column), direction }],
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
groupBy(
|
|
420
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
421
|
+
...columns: QueryColumnInput<columnTypes>[]
|
|
422
|
+
): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
|
|
423
|
+
return this.#clone({
|
|
424
|
+
groupBy: [...this.#state.groupBy, ...columns.map((column) => normalizeColumnInput(column))],
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
limit(
|
|
429
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
430
|
+
value: number,
|
|
431
|
+
): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
|
|
432
|
+
return this.#clone({ limit: value })
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
offset(
|
|
436
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
437
|
+
value: number,
|
|
438
|
+
): Query<source, columnTypes, row, loaded, QueryAllPhase<phase>> {
|
|
439
|
+
return this.#clone({ offset: value })
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
with<relations extends RelationMapForSourceName<QuerySourceTableName<source>>>(
|
|
443
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
444
|
+
relations: relations,
|
|
445
|
+
): Query<source, columnTypes, row, loaded & LoadedRelationMap<relations>, QueryAllPhase<phase>> {
|
|
446
|
+
return this.#clone({
|
|
447
|
+
with: {
|
|
448
|
+
...this.#state.with,
|
|
449
|
+
...relations,
|
|
450
|
+
},
|
|
451
|
+
}) as Query<
|
|
452
|
+
source,
|
|
453
|
+
columnTypes,
|
|
454
|
+
row,
|
|
455
|
+
loaded & LoadedRelationMap<relations>,
|
|
456
|
+
QueryAllPhase<phase>
|
|
457
|
+
>
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
all(
|
|
461
|
+
this: Query<source, columnTypes, row, loaded, BoundQueryPhase<'all'>>,
|
|
462
|
+
): Promise<Array<row & loaded>> {
|
|
463
|
+
return this.#boundRuntime().exec(this) as Promise<Array<row & loaded>>
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
first(
|
|
467
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
468
|
+
): QueryTerminalResult<
|
|
469
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
470
|
+
'first',
|
|
471
|
+
(row & loaded) | null
|
|
472
|
+
> {
|
|
473
|
+
return this.#resolveTerminal({ kind: 'first' })
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
find(
|
|
477
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
478
|
+
value: PrimaryKeyInputForRow<row, QuerySourcePrimaryKey<source>>,
|
|
479
|
+
): QueryTerminalResult<
|
|
480
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
481
|
+
'find',
|
|
482
|
+
(row & loaded) | null
|
|
483
|
+
> {
|
|
484
|
+
return this.#resolveTerminal({ kind: 'find', value })
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
count(
|
|
488
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
489
|
+
): QueryTerminalResult<
|
|
490
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
491
|
+
'count',
|
|
492
|
+
number
|
|
493
|
+
> {
|
|
494
|
+
return this.#resolveTerminal({ kind: 'count' })
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
exists(
|
|
498
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
499
|
+
): QueryTerminalResult<
|
|
500
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
501
|
+
'exists',
|
|
502
|
+
boolean
|
|
503
|
+
> {
|
|
504
|
+
return this.#resolveTerminal({ kind: 'exists' })
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
insert(
|
|
508
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
509
|
+
values: Partial<row>,
|
|
510
|
+
options?: InsertQueryOptions<row>,
|
|
511
|
+
): QueryTerminalResult<
|
|
512
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
513
|
+
'insert',
|
|
514
|
+
WriteResult | WriteRowResult<row>
|
|
515
|
+
> {
|
|
516
|
+
assertWriteState(this.#state, 'insert', {
|
|
517
|
+
where: false,
|
|
518
|
+
orderBy: false,
|
|
519
|
+
limit: false,
|
|
520
|
+
offset: false,
|
|
521
|
+
})
|
|
522
|
+
|
|
523
|
+
return this.#resolveTerminal({ kind: 'insert', values, options })
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
insertMany(
|
|
527
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
528
|
+
values: Partial<row>[],
|
|
529
|
+
options?: InsertQueryOptions<row>,
|
|
530
|
+
): QueryTerminalResult<
|
|
531
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
532
|
+
'insertMany',
|
|
533
|
+
WriteResult | WriteRowsResult<row>
|
|
534
|
+
> {
|
|
535
|
+
assertWriteState(this.#state, 'insertMany', {
|
|
536
|
+
where: false,
|
|
537
|
+
orderBy: false,
|
|
538
|
+
limit: false,
|
|
539
|
+
offset: false,
|
|
540
|
+
})
|
|
541
|
+
|
|
542
|
+
return this.#resolveTerminal({ kind: 'insertMany', values, options })
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
update(
|
|
546
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
547
|
+
changes: Partial<row>,
|
|
548
|
+
options?: InsertQueryOptions<row>,
|
|
549
|
+
): QueryTerminalResult<
|
|
550
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
551
|
+
'update',
|
|
552
|
+
WriteResult | WriteRowsResult<row>
|
|
553
|
+
> {
|
|
554
|
+
assertWriteState(this.#state, 'update', {
|
|
555
|
+
where: true,
|
|
556
|
+
orderBy: true,
|
|
557
|
+
limit: true,
|
|
558
|
+
offset: true,
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
return this.#resolveTerminal({ kind: 'update', changes, options })
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
delete(
|
|
565
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
566
|
+
options?: DeleteQueryOptions<row>,
|
|
567
|
+
): QueryTerminalResult<
|
|
568
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
569
|
+
'delete',
|
|
570
|
+
WriteResult | WriteRowsResult<row>
|
|
571
|
+
> {
|
|
572
|
+
assertWriteState(this.#state, 'delete', {
|
|
573
|
+
where: true,
|
|
574
|
+
orderBy: true,
|
|
575
|
+
limit: true,
|
|
576
|
+
offset: true,
|
|
577
|
+
})
|
|
578
|
+
|
|
579
|
+
return this.#resolveTerminal({ kind: 'delete', options })
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
upsert(
|
|
583
|
+
this: Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
584
|
+
values: Partial<row>,
|
|
585
|
+
options?: UpsertQueryOptions<row>,
|
|
586
|
+
): QueryTerminalResult<
|
|
587
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
588
|
+
'upsert',
|
|
589
|
+
WriteResult | WriteRowResult<row>
|
|
590
|
+
> {
|
|
591
|
+
assertWriteState(this.#state, 'upsert', {
|
|
592
|
+
where: false,
|
|
593
|
+
orderBy: false,
|
|
594
|
+
limit: false,
|
|
595
|
+
offset: false,
|
|
596
|
+
})
|
|
597
|
+
|
|
598
|
+
return this.#resolveTerminal({ kind: 'upsert', values, options })
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
[querySnapshot](): QuerySnapshot<source, row, QueryPhaseMode<phase>> {
|
|
602
|
+
return this.#snapshot()
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
#resolveTerminal<nextMode extends QueryExecutionMode, result>(
|
|
606
|
+
plan: QueryPlan<row, QuerySourcePrimaryKey<source>, nextMode>,
|
|
607
|
+
): QueryTerminalResult<
|
|
608
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
609
|
+
nextMode,
|
|
610
|
+
result
|
|
611
|
+
> {
|
|
612
|
+
let next = this.#withPlan(plan)
|
|
613
|
+
return (this.#runtime ? this.#runtime.exec(next) : next) as QueryTerminalResult<
|
|
614
|
+
Query<source, columnTypes, row, loaded, QueryAllPhase<phase>>,
|
|
615
|
+
nextMode,
|
|
616
|
+
result
|
|
617
|
+
>
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
[bindQueryRuntime](
|
|
621
|
+
runtime: QueryRuntime,
|
|
622
|
+
): Query<source, columnTypes, row, loaded, BoundQueryPhase<QueryPhaseMode<phase>>> {
|
|
623
|
+
return Query.#createInternal<
|
|
624
|
+
source,
|
|
625
|
+
columnTypes,
|
|
626
|
+
row,
|
|
627
|
+
loaded,
|
|
628
|
+
BoundQueryPhase<QueryPhaseMode<phase>>
|
|
629
|
+
>(
|
|
630
|
+
this.#table,
|
|
631
|
+
this.#state,
|
|
632
|
+
this.#plan as QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>,
|
|
633
|
+
runtime,
|
|
634
|
+
)
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
#clone(patch: Partial<QueryState>): Query<source, columnTypes, row, loaded, phase> {
|
|
638
|
+
return Query.#createInternal<source, columnTypes, row, loaded, phase>(
|
|
639
|
+
this.#table,
|
|
640
|
+
{
|
|
641
|
+
select: patch.select ?? cloneSelection(this.#state.select),
|
|
642
|
+
distinct: patch.distinct ?? this.#state.distinct,
|
|
643
|
+
joins: patch.joins ? [...patch.joins] : [...this.#state.joins],
|
|
644
|
+
where: patch.where ? [...patch.where] : [...this.#state.where],
|
|
645
|
+
groupBy: patch.groupBy ? [...patch.groupBy] : [...this.#state.groupBy],
|
|
646
|
+
having: patch.having ? [...patch.having] : [...this.#state.having],
|
|
647
|
+
orderBy: patch.orderBy ? [...patch.orderBy] : [...this.#state.orderBy],
|
|
648
|
+
limit: patch.limit === undefined ? this.#state.limit : patch.limit,
|
|
649
|
+
offset: patch.offset === undefined ? this.#state.offset : patch.offset,
|
|
650
|
+
with: patch.with ? { ...patch.with } : { ...this.#state.with },
|
|
651
|
+
},
|
|
652
|
+
this.#plan as QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>,
|
|
653
|
+
this.#runtime,
|
|
654
|
+
)
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
#withPlan<nextMode extends QueryExecutionMode>(
|
|
658
|
+
plan: QueryPlan<row, QuerySourcePrimaryKey<source>, nextMode>,
|
|
659
|
+
): Query<source, columnTypes, row, loaded, QueryNextPhase<phase, nextMode>> {
|
|
660
|
+
return Query.#createInternal<source, columnTypes, row, loaded, QueryNextPhase<phase, nextMode>>(
|
|
661
|
+
this.#table,
|
|
662
|
+
this.#state,
|
|
663
|
+
plan,
|
|
664
|
+
this.#runtime,
|
|
665
|
+
)
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
#snapshot(): QuerySnapshot<source, row, QueryPhaseMode<phase>> {
|
|
669
|
+
return {
|
|
670
|
+
table: this.#table,
|
|
671
|
+
state: cloneQueryState(this.#state),
|
|
672
|
+
plan: cloneQueryPlan(
|
|
673
|
+
this.#plan as QueryPlan<row, QuerySourcePrimaryKey<source>>,
|
|
674
|
+
) as QueryPlan<row, QuerySourcePrimaryKey<source>, QueryPhaseMode<phase>>,
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
#boundRuntime(): QueryRuntime {
|
|
679
|
+
if (!this.#runtime) {
|
|
680
|
+
throw new DataTableQueryError('Use db.exec(query) to execute an unbound Query')
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
return this.#runtime
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
export function query<
|
|
688
|
+
tableName extends string,
|
|
689
|
+
row extends Record<string, unknown>,
|
|
690
|
+
primaryKey extends readonly (keyof row & string)[],
|
|
691
|
+
>(
|
|
692
|
+
table: QueryTableInput<tableName, row, primaryKey>,
|
|
693
|
+
): Query<
|
|
694
|
+
QueryTableInput<tableName, row, primaryKey>,
|
|
695
|
+
QueryColumnTypeMapFromRow<tableName, row>,
|
|
696
|
+
row,
|
|
697
|
+
{},
|
|
698
|
+
UnboundQueryPhase<'all'>
|
|
699
|
+
> {
|
|
700
|
+
return new Query(table) as Query<
|
|
701
|
+
QueryTableInput<tableName, row, primaryKey>,
|
|
702
|
+
QueryColumnTypeMapFromRow<tableName, row>,
|
|
703
|
+
row,
|
|
704
|
+
{},
|
|
705
|
+
UnboundQueryPhase<'all'>
|
|
706
|
+
>
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
export function cloneQueryState(state: QueryState): QueryState {
|
|
710
|
+
return {
|
|
711
|
+
select: cloneSelection(state.select),
|
|
712
|
+
distinct: state.distinct,
|
|
713
|
+
joins: [...state.joins],
|
|
714
|
+
where: [...state.where],
|
|
715
|
+
groupBy: [...state.groupBy],
|
|
716
|
+
having: [...state.having],
|
|
717
|
+
orderBy: [...state.orderBy],
|
|
718
|
+
limit: state.limit,
|
|
719
|
+
offset: state.offset,
|
|
720
|
+
with: { ...state.with },
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
function createInitialQueryState(): QueryState {
|
|
725
|
+
return {
|
|
726
|
+
select: '*',
|
|
727
|
+
distinct: false,
|
|
728
|
+
joins: [],
|
|
729
|
+
where: [],
|
|
730
|
+
groupBy: [],
|
|
731
|
+
having: [],
|
|
732
|
+
orderBy: [],
|
|
733
|
+
with: {},
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
function cloneQueryPlan<row extends Record<string, unknown>, primaryKey extends readonly string[]>(
|
|
738
|
+
plan: QueryPlan<row, primaryKey>,
|
|
739
|
+
): QueryPlan<row, primaryKey> {
|
|
740
|
+
switch (plan.kind) {
|
|
741
|
+
case 'all':
|
|
742
|
+
return { kind: 'all' } as QueryPlan<row, primaryKey>
|
|
743
|
+
case 'first':
|
|
744
|
+
return { kind: 'first' } as QueryPlan<row, primaryKey>
|
|
745
|
+
case 'find':
|
|
746
|
+
return {
|
|
747
|
+
kind: 'find',
|
|
748
|
+
value: clonePrimaryKeyValue(plan.value) as PrimaryKeyInputForRow<row, primaryKey>,
|
|
749
|
+
} as QueryPlan<row, primaryKey>
|
|
750
|
+
case 'count':
|
|
751
|
+
return { kind: 'count' } as QueryPlan<row, primaryKey>
|
|
752
|
+
case 'exists':
|
|
753
|
+
return { kind: 'exists' } as QueryPlan<row, primaryKey>
|
|
754
|
+
case 'insert':
|
|
755
|
+
return {
|
|
756
|
+
kind: 'insert',
|
|
757
|
+
values: { ...plan.values },
|
|
758
|
+
options: plan.options ? { ...plan.options } : undefined,
|
|
759
|
+
} as QueryPlan<row, primaryKey>
|
|
760
|
+
case 'insertMany':
|
|
761
|
+
return {
|
|
762
|
+
kind: 'insertMany',
|
|
763
|
+
values: plan.values.map((value: Partial<row>) => ({ ...value })),
|
|
764
|
+
options: plan.options ? { ...plan.options } : undefined,
|
|
765
|
+
} as QueryPlan<row, primaryKey>
|
|
766
|
+
case 'update':
|
|
767
|
+
return {
|
|
768
|
+
kind: 'update',
|
|
769
|
+
changes: { ...plan.changes },
|
|
770
|
+
options: plan.options ? { ...plan.options } : undefined,
|
|
771
|
+
} as QueryPlan<row, primaryKey>
|
|
772
|
+
case 'delete':
|
|
773
|
+
return {
|
|
774
|
+
kind: 'delete',
|
|
775
|
+
options: plan.options ? { ...plan.options } : undefined,
|
|
776
|
+
} as QueryPlan<row, primaryKey>
|
|
777
|
+
case 'upsert':
|
|
778
|
+
return {
|
|
779
|
+
kind: 'upsert',
|
|
780
|
+
values: { ...plan.values },
|
|
781
|
+
options: plan.options
|
|
782
|
+
? {
|
|
783
|
+
...plan.options,
|
|
784
|
+
conflictTarget: plan.options.conflictTarget
|
|
785
|
+
? [...plan.options.conflictTarget]
|
|
786
|
+
: undefined,
|
|
787
|
+
update: plan.options.update ? { ...plan.options.update } : undefined,
|
|
788
|
+
}
|
|
789
|
+
: undefined,
|
|
790
|
+
} as QueryPlan<row, primaryKey>
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function clonePrimaryKeyValue(value: unknown): unknown {
|
|
795
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
796
|
+
return value
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
return { ...value }
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function cloneSelection(selection: '*' | SelectColumn[]): '*' | SelectColumn[] {
|
|
803
|
+
if (selection === '*') {
|
|
804
|
+
return '*'
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
return selection.map((column) => ({ ...column }))
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
type WriteStatePolicy = {
|
|
811
|
+
where: boolean
|
|
812
|
+
orderBy: boolean
|
|
813
|
+
limit: boolean
|
|
814
|
+
offset: boolean
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
function assertWriteState(
|
|
818
|
+
state: QueryState,
|
|
819
|
+
operation: 'insert' | 'insertMany' | 'update' | 'delete' | 'upsert',
|
|
820
|
+
policy: WriteStatePolicy,
|
|
821
|
+
): void {
|
|
822
|
+
let unsupported: string[] = []
|
|
823
|
+
|
|
824
|
+
if (state.select !== '*') unsupported.push('select()')
|
|
825
|
+
if (state.distinct) unsupported.push('distinct()')
|
|
826
|
+
if (state.joins.length > 0) unsupported.push('join()')
|
|
827
|
+
if (state.groupBy.length > 0) unsupported.push('groupBy()')
|
|
828
|
+
if (state.having.length > 0) unsupported.push('having()')
|
|
829
|
+
if (Object.keys(state.with).length > 0) unsupported.push('with()')
|
|
830
|
+
if (!policy.where && state.where.length > 0) unsupported.push('where()')
|
|
831
|
+
if (!policy.orderBy && state.orderBy.length > 0) unsupported.push('orderBy()')
|
|
832
|
+
if (!policy.limit && state.limit !== undefined) unsupported.push('limit()')
|
|
833
|
+
if (!policy.offset && state.offset !== undefined) unsupported.push('offset()')
|
|
834
|
+
|
|
835
|
+
if (unsupported.length > 0) {
|
|
836
|
+
throw new DataTableQueryError(
|
|
837
|
+
operation + '() does not support these query modifiers: ' + unsupported.join(', '),
|
|
838
|
+
)
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
type ResolvedPredicateColumn = {
|
|
843
|
+
tableName: string
|
|
844
|
+
columnName: string
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function createPredicateColumnResolver(
|
|
848
|
+
tables: AnyTable[],
|
|
849
|
+
): (column: string) => ResolvedPredicateColumn {
|
|
850
|
+
let qualifiedColumns = new Map<string, ResolvedPredicateColumn>()
|
|
851
|
+
let unqualifiedColumns = new Map<string, ResolvedPredicateColumn>()
|
|
852
|
+
let ambiguousColumns = new Set<string>()
|
|
853
|
+
|
|
854
|
+
for (let table of tables) {
|
|
855
|
+
let tableColumns = getTableColumns(table)
|
|
856
|
+
let tableName = getTableName(table)
|
|
857
|
+
|
|
858
|
+
for (let columnName in tableColumns) {
|
|
859
|
+
if (!Object.prototype.hasOwnProperty.call(tableColumns, columnName)) {
|
|
860
|
+
continue
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
let resolvedColumn: ResolvedPredicateColumn = {
|
|
864
|
+
tableName,
|
|
865
|
+
columnName,
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
qualifiedColumns.set(tableName + '.' + columnName, resolvedColumn)
|
|
869
|
+
|
|
870
|
+
if (ambiguousColumns.has(columnName)) {
|
|
871
|
+
continue
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
if (unqualifiedColumns.has(columnName)) {
|
|
875
|
+
unqualifiedColumns.delete(columnName)
|
|
876
|
+
ambiguousColumns.add(columnName)
|
|
877
|
+
continue
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
unqualifiedColumns.set(columnName, resolvedColumn)
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return function resolveColumn(column: string): ResolvedPredicateColumn {
|
|
885
|
+
let qualified = qualifiedColumns.get(column)
|
|
886
|
+
if (qualified) return qualified
|
|
887
|
+
|
|
888
|
+
if (column.includes('.')) {
|
|
889
|
+
throw new DataTableQueryError('Unknown predicate column "' + column + '"')
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
if (ambiguousColumns.has(column)) {
|
|
893
|
+
throw new DataTableQueryError(
|
|
894
|
+
'Ambiguous predicate column "' + column + '". Use a qualified column name',
|
|
895
|
+
)
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
let unqualified = unqualifiedColumns.get(column)
|
|
899
|
+
|
|
900
|
+
if (!unqualified) {
|
|
901
|
+
throw new DataTableQueryError('Unknown predicate column "' + column + '"')
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
return unqualified
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function normalizePredicateValues(
|
|
909
|
+
predicate: Predicate,
|
|
910
|
+
resolveColumn: (column: string) => ResolvedPredicateColumn,
|
|
911
|
+
): Predicate {
|
|
912
|
+
if (predicate.type === 'comparison') {
|
|
913
|
+
let column = resolveColumn(predicate.column)
|
|
914
|
+
|
|
915
|
+
if (predicate.valueType === 'column') {
|
|
916
|
+
resolveColumn(predicate.value)
|
|
917
|
+
return predicate
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (predicate.operator === 'in' || predicate.operator === 'notIn') {
|
|
921
|
+
if (!Array.isArray(predicate.value)) {
|
|
922
|
+
throw new DataTableValidationError(
|
|
923
|
+
'Invalid filter value for column "' +
|
|
924
|
+
column.columnName +
|
|
925
|
+
'" in table "' +
|
|
926
|
+
column.tableName +
|
|
927
|
+
'"',
|
|
928
|
+
[{ message: 'Expected an array value for "' + predicate.operator + '" predicate' }],
|
|
929
|
+
{
|
|
930
|
+
metadata: {
|
|
931
|
+
table: column.tableName,
|
|
932
|
+
column: column.columnName,
|
|
933
|
+
},
|
|
934
|
+
},
|
|
935
|
+
)
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
return predicate
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
return predicate
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
if (predicate.type === 'between') {
|
|
945
|
+
resolveColumn(predicate.column)
|
|
946
|
+
return predicate
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
if (predicate.type === 'null') {
|
|
950
|
+
resolveColumn(predicate.column)
|
|
951
|
+
return predicate
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
return {
|
|
955
|
+
...predicate,
|
|
956
|
+
predicates: predicate.predicates.map((child) => normalizePredicateValues(child, resolveColumn)),
|
|
957
|
+
}
|
|
958
|
+
}
|