@remix-run/data-table 0.1.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/README.md +306 -55
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/lib/adapter.d.ts +386 -16
- package/dist/lib/adapter.d.ts.map +1 -1
- 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 +141 -238
- package/dist/lib/database.d.ts.map +1 -1
- package/dist/lib/database.js +73 -1122
- package/dist/lib/errors.d.ts +9 -0
- package/dist/lib/errors.d.ts.map +1 -1
- package/dist/lib/errors.js +9 -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 +3 -0
- package/dist/lib/operators.d.ts.map +1 -1
- 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 +0 -1
- package/dist/lib/references.d.ts.map +1 -1
- 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 +23 -6
- package/dist/lib/sql.d.ts.map +1 -1
- package/dist/lib/sql.js +19 -5
- package/dist/lib/table.d.ts +355 -40
- package/dist/lib/table.d.ts.map +1 -1
- package/dist/lib/table.js +113 -90
- 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 +23 -8
- package/src/index.ts +93 -10
- package/src/lib/adapter.ts +469 -25
- 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 +246 -1848
- package/src/lib/errors.ts +10 -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 +3 -0
- package/src/lib/query.ts +958 -0
- package/src/lib/references.ts +0 -1
- package/src/lib/sql-helpers.ts +146 -0
- package/src/lib/sql.ts +23 -6
- package/src/lib/table.ts +484 -156
- 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/database.ts
CHANGED
|
@@ -1,75 +1,55 @@
|
|
|
1
|
-
import { parseSafe } from '@remix-run/data-schema'
|
|
2
|
-
import type { Schema } from '@remix-run/data-schema'
|
|
3
|
-
|
|
4
1
|
import type {
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
ColumnDefinition,
|
|
3
|
+
DataManipulationOperation,
|
|
4
|
+
DataManipulationResult,
|
|
7
5
|
DatabaseAdapter,
|
|
8
|
-
DeleteStatement,
|
|
9
|
-
ExistsStatement,
|
|
10
|
-
InsertManyStatement,
|
|
11
|
-
InsertStatement,
|
|
12
|
-
JoinClause,
|
|
13
|
-
JoinType,
|
|
14
|
-
ReturningSelection,
|
|
15
|
-
SelectColumn,
|
|
16
|
-
SelectStatement,
|
|
17
6
|
TransactionOptions,
|
|
18
7
|
TransactionToken,
|
|
19
|
-
UpdateStatement,
|
|
20
|
-
UpsertStatement,
|
|
21
8
|
} from './adapter.ts'
|
|
22
|
-
import {
|
|
9
|
+
import type { ColumnBuilder } from './column.ts'
|
|
10
|
+
import { DataTableAdapterError, DataTableQueryError } from './errors.ts'
|
|
11
|
+
import { executeOperation, type QueryExecutionContext } from './database/execution-context.ts'
|
|
12
|
+
import {
|
|
13
|
+
asQueryTableInput,
|
|
14
|
+
getPrimaryKeyWhere,
|
|
15
|
+
getPrimaryKeyWhereFromRow,
|
|
16
|
+
normalizeOrderByInput,
|
|
17
|
+
resolveCreateRowWhere,
|
|
18
|
+
toWriteResult,
|
|
19
|
+
} from './database/helpers.ts'
|
|
20
|
+
import { executeQuery } from './database/query-execution.ts'
|
|
21
|
+
import type {
|
|
22
|
+
AnyQuery,
|
|
23
|
+
BoundQueryPhase,
|
|
24
|
+
Query as QueryObject,
|
|
25
|
+
QueryExecutionResult,
|
|
26
|
+
} from './query.ts'
|
|
27
|
+
import { bindQueryRuntime, query as createQuery } from './query.ts'
|
|
28
|
+
import type { ColumnInput, NormalizeColumnInput, TableMetadataLike } from './references.ts'
|
|
29
|
+
import type { SqlStatement } from './sql.ts'
|
|
30
|
+
import { isSqlStatement, rawSql } from './sql.ts'
|
|
23
31
|
import type {
|
|
24
32
|
AnyRelation,
|
|
25
33
|
AnyTable,
|
|
26
34
|
LoadedRelationMap,
|
|
27
|
-
OrderByClause,
|
|
28
35
|
OrderDirection,
|
|
29
36
|
PrimaryKeyInput,
|
|
30
|
-
Relation,
|
|
31
37
|
TableName,
|
|
32
38
|
TablePrimaryKey,
|
|
33
39
|
TableRow,
|
|
34
40
|
TableRowWith,
|
|
35
|
-
|
|
41
|
+
TableValidate,
|
|
36
42
|
tableMetadataKey,
|
|
43
|
+
TimestampConfig,
|
|
37
44
|
} from './table.ts'
|
|
38
|
-
import {
|
|
39
|
-
getCompositeKey,
|
|
40
|
-
getPrimaryKeyObject,
|
|
41
|
-
getTableColumns,
|
|
42
|
-
getTableName,
|
|
43
|
-
getTablePrimaryKey,
|
|
44
|
-
getTableTimestamps,
|
|
45
|
-
validatePartialRow,
|
|
46
|
-
} from './table.ts'
|
|
47
|
-
import type { Predicate, WhereInput } from './operators.ts'
|
|
48
|
-
import { and, eq, inList, normalizeWhereInput, or } from './operators.ts'
|
|
49
|
-
import type { SqlStatement } from './sql.ts'
|
|
50
|
-
import { rawSql, isSqlStatement } from './sql.ts'
|
|
51
|
-
import type { AdapterStatement } from './adapter.ts'
|
|
45
|
+
import { getTableName } from './table.ts'
|
|
52
46
|
import type { Pretty } from './types.ts'
|
|
53
|
-
import {
|
|
54
|
-
import type { ColumnInput, NormalizeColumnInput, TableMetadataLike } from './references.ts'
|
|
55
|
-
|
|
56
|
-
type QueryState = {
|
|
57
|
-
select: '*' | SelectColumn[]
|
|
58
|
-
distinct: boolean
|
|
59
|
-
joins: JoinClause[]
|
|
60
|
-
where: Predicate<string>[]
|
|
61
|
-
groupBy: string[]
|
|
62
|
-
having: Predicate<string>[]
|
|
63
|
-
orderBy: OrderByClause[]
|
|
64
|
-
limit?: number
|
|
65
|
-
offset?: number
|
|
66
|
-
with: Record<string, AnyRelation>
|
|
67
|
-
}
|
|
47
|
+
import type { WhereInput } from './operators.ts'
|
|
68
48
|
|
|
69
|
-
type TableColumnName<table extends AnyTable> = keyof TableRow<table> & string
|
|
70
|
-
type QualifiedTableColumnName<table extends AnyTable> =
|
|
49
|
+
export type TableColumnName<table extends AnyTable> = keyof TableRow<table> & string
|
|
50
|
+
export type QualifiedTableColumnName<table extends AnyTable> =
|
|
71
51
|
`${TableName<table>}.${TableColumnName<table>}`
|
|
72
|
-
type QueryColumnName<table extends AnyTable> =
|
|
52
|
+
export type QueryColumnName<table extends AnyTable> =
|
|
73
53
|
| TableColumnName<table>
|
|
74
54
|
| QualifiedTableColumnName<table>
|
|
75
55
|
|
|
@@ -79,7 +59,10 @@ type QualifiedRowColumnName<
|
|
|
79
59
|
row extends Record<string, unknown>,
|
|
80
60
|
> = `${tableName}.${RowColumnName<row>}`
|
|
81
61
|
|
|
82
|
-
type QueryColumnTypeMapFromRow<
|
|
62
|
+
export type QueryColumnTypeMapFromRow<
|
|
63
|
+
tableName extends string,
|
|
64
|
+
row extends Record<string, unknown>,
|
|
65
|
+
> = {
|
|
83
66
|
[column in
|
|
84
67
|
| RowColumnName<row>
|
|
85
68
|
| QualifiedRowColumnName<tableName, row>]: column extends RowColumnName<row>
|
|
@@ -89,11 +72,11 @@ type QueryColumnTypeMapFromRow<tableName extends string, row extends Record<stri
|
|
|
89
72
|
: never
|
|
90
73
|
}
|
|
91
74
|
|
|
92
|
-
type QueryColumnTypeMap<table extends AnyTable> = Pretty<
|
|
75
|
+
export type QueryColumnTypeMap<table extends AnyTable> = Pretty<
|
|
93
76
|
QueryColumnTypeMapFromRow<TableName<table>, TableRow<table>>
|
|
94
77
|
>
|
|
95
78
|
|
|
96
|
-
type MergeColumnTypeMaps<
|
|
79
|
+
export type MergeColumnTypeMaps<
|
|
97
80
|
left extends Record<string, unknown>,
|
|
98
81
|
right extends Record<string, unknown>,
|
|
99
82
|
> = Pretty<{
|
|
@@ -106,13 +89,16 @@ type MergeColumnTypeMaps<
|
|
|
106
89
|
: never
|
|
107
90
|
}>
|
|
108
91
|
|
|
109
|
-
type QueryColumns<columnTypes extends Record<string, unknown>> = Extract<
|
|
92
|
+
export type QueryColumns<columnTypes extends Record<string, unknown>> = Extract<
|
|
93
|
+
keyof columnTypes,
|
|
94
|
+
string
|
|
95
|
+
>
|
|
110
96
|
|
|
111
|
-
type QueryColumnInput<columnTypes extends Record<string, unknown>> = ColumnInput<
|
|
97
|
+
export type QueryColumnInput<columnTypes extends Record<string, unknown>> = ColumnInput<
|
|
112
98
|
QueryColumns<columnTypes>
|
|
113
99
|
>
|
|
114
100
|
|
|
115
|
-
type SelectedAliasRow<
|
|
101
|
+
export type SelectedAliasRow<
|
|
116
102
|
columnTypes extends Record<string, unknown>,
|
|
117
103
|
selection extends Record<string, QueryColumnInput<columnTypes>>,
|
|
118
104
|
> = Pretty<{
|
|
@@ -121,13 +107,7 @@ type SelectedAliasRow<
|
|
|
121
107
|
: never
|
|
122
108
|
}>
|
|
123
109
|
|
|
124
|
-
type
|
|
125
|
-
value: number
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const executeStatement = Symbol('executeStatement')
|
|
129
|
-
|
|
130
|
-
type RelationMapForSourceName<tableName extends string> = Record<
|
|
110
|
+
export type RelationMapForSourceName<tableName extends string> = Record<
|
|
131
111
|
string,
|
|
132
112
|
AnyRelation & {
|
|
133
113
|
sourceTable: {
|
|
@@ -138,7 +118,7 @@ type RelationMapForSourceName<tableName extends string> = Record<
|
|
|
138
118
|
}
|
|
139
119
|
>
|
|
140
120
|
|
|
141
|
-
type PrimaryKeyInputForRow<
|
|
121
|
+
export type PrimaryKeyInputForRow<
|
|
142
122
|
row extends Record<string, unknown>,
|
|
143
123
|
primaryKey extends readonly string[],
|
|
144
124
|
> = primaryKey extends readonly [infer column extends keyof row & string]
|
|
@@ -147,8 +127,11 @@ type PrimaryKeyInputForRow<
|
|
|
147
127
|
[column in primaryKey[number] & keyof row]: row[column]
|
|
148
128
|
}
|
|
149
129
|
|
|
150
|
-
type ReturningInput<row extends Record<string, unknown>> = '*' | (keyof row & string)[]
|
|
130
|
+
export type ReturningInput<row extends Record<string, unknown>> = '*' | (keyof row & string)[]
|
|
151
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Table-like metadata accepted by `database.query()`.
|
|
134
|
+
*/
|
|
152
135
|
export type QueryTableInput<
|
|
153
136
|
tableName extends string,
|
|
154
137
|
row extends Record<string, unknown>,
|
|
@@ -156,76 +139,94 @@ export type QueryTableInput<
|
|
|
156
139
|
> = TableMetadataLike<
|
|
157
140
|
tableName,
|
|
158
141
|
{
|
|
159
|
-
[column in keyof row & string]:
|
|
142
|
+
[column in keyof row & string]: ColumnBuilder<row[column]>
|
|
160
143
|
},
|
|
161
144
|
primaryKey,
|
|
162
145
|
TimestampConfig | null
|
|
163
146
|
> & {
|
|
164
|
-
|
|
147
|
+
[tableMetadataKey]: {
|
|
148
|
+
name: tableName
|
|
149
|
+
columns: {
|
|
150
|
+
[column in keyof row & string]: ColumnBuilder<row[column]>
|
|
151
|
+
}
|
|
152
|
+
primaryKey: primaryKey
|
|
153
|
+
timestamps: TimestampConfig | null
|
|
154
|
+
columnDefinitions: Record<string, ColumnDefinition>
|
|
155
|
+
validate?: TableValidate<Record<string, unknown>>
|
|
156
|
+
}
|
|
165
157
|
} & Record<string, unknown>
|
|
166
158
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
primaryKey extends readonly (keyof row & string)[],
|
|
171
|
-
loaded extends Record<string, unknown> = {},
|
|
172
|
-
> = QueryBuilder<
|
|
173
|
-
Pretty<QueryColumnTypeMapFromRow<tableName, row>>,
|
|
174
|
-
row,
|
|
175
|
-
loaded,
|
|
176
|
-
tableName,
|
|
177
|
-
primaryKey
|
|
178
|
-
>
|
|
179
|
-
|
|
180
|
-
export type QueryMethod = <
|
|
181
|
-
tableName extends string,
|
|
182
|
-
row extends Record<string, unknown>,
|
|
183
|
-
primaryKey extends readonly (keyof row & string)[],
|
|
184
|
-
>(
|
|
185
|
-
table: QueryTableInput<tableName, row, primaryKey>,
|
|
186
|
-
) => QueryBuilderFor<tableName, row, primaryKey>
|
|
187
|
-
|
|
159
|
+
/**
|
|
160
|
+
* Result metadata for write operations that do not return rows.
|
|
161
|
+
*/
|
|
188
162
|
export type WriteResult = {
|
|
189
163
|
affectedRows: number
|
|
190
164
|
insertId?: unknown
|
|
191
165
|
}
|
|
192
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Result metadata for write operations that return multiple rows.
|
|
169
|
+
*/
|
|
193
170
|
export type WriteRowsResult<row> = {
|
|
194
171
|
affectedRows: number
|
|
195
172
|
insertId?: unknown
|
|
196
173
|
rows: row[]
|
|
197
174
|
}
|
|
198
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Result metadata for write operations that return a single row.
|
|
178
|
+
*/
|
|
199
179
|
export type WriteRowResult<row> = {
|
|
200
180
|
affectedRows: number
|
|
201
181
|
insertId?: unknown
|
|
202
182
|
row: row | null
|
|
203
183
|
}
|
|
204
184
|
|
|
185
|
+
/**
|
|
186
|
+
* Queryable column type map for a concrete table.
|
|
187
|
+
*/
|
|
205
188
|
export type QueryColumnTypesForTable<table extends AnyTable> = QueryColumnTypeMap<table>
|
|
206
189
|
|
|
190
|
+
/**
|
|
191
|
+
* Query type produced for a concrete table.
|
|
192
|
+
*/
|
|
207
193
|
export type QueryForTable<
|
|
208
194
|
table extends AnyTable,
|
|
209
195
|
loaded extends Record<string, unknown> = {},
|
|
210
|
-
> =
|
|
196
|
+
> = QueryObject<
|
|
197
|
+
QueryTableInput<TableName<table>, TableRow<table>, TablePrimaryKey<table>>,
|
|
211
198
|
QueryColumnTypesForTable<table>,
|
|
212
199
|
TableRow<table>,
|
|
213
200
|
loaded,
|
|
214
|
-
|
|
215
|
-
TablePrimaryKey<table>
|
|
201
|
+
BoundQueryPhase<'all'>
|
|
216
202
|
>
|
|
217
203
|
|
|
204
|
+
/**
|
|
205
|
+
* Column names accepted in single-table queries.
|
|
206
|
+
*/
|
|
218
207
|
export type SingleTableColumn<table extends AnyTable> = QueryColumns<QueryColumnTypeMap<table>>
|
|
219
208
|
|
|
209
|
+
/**
|
|
210
|
+
* `where` input accepted in single-table queries.
|
|
211
|
+
*/
|
|
220
212
|
export type SingleTableWhere<table extends AnyTable> = WhereInput<SingleTableColumn<table>>
|
|
221
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Tuple form accepted by `orderBy` for a single table.
|
|
216
|
+
*/
|
|
222
217
|
export type OrderByTuple<table extends AnyTable> = [
|
|
223
218
|
column: SingleTableColumn<table>,
|
|
224
219
|
direction?: OrderDirection,
|
|
225
220
|
]
|
|
226
221
|
|
|
222
|
+
/**
|
|
223
|
+
* `orderBy` input accepted in single-table queries.
|
|
224
|
+
*/
|
|
227
225
|
export type OrderByInput<table extends AnyTable> = OrderByTuple<table> | OrderByTuple<table>[]
|
|
228
226
|
|
|
227
|
+
/**
|
|
228
|
+
* Options for loading many rows from a table.
|
|
229
|
+
*/
|
|
229
230
|
export type FindManyOptions<
|
|
230
231
|
table extends AnyTable,
|
|
231
232
|
relations extends RelationMapForSourceName<TableName<table>> = {},
|
|
@@ -237,6 +238,9 @@ export type FindManyOptions<
|
|
|
237
238
|
with?: relations
|
|
238
239
|
}
|
|
239
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Options for loading a single row from a table.
|
|
243
|
+
*/
|
|
240
244
|
export type FindOneOptions<
|
|
241
245
|
table extends AnyTable,
|
|
242
246
|
relations extends RelationMapForSourceName<TableName<table>> = {},
|
|
@@ -244,6 +248,9 @@ export type FindOneOptions<
|
|
|
244
248
|
where: SingleTableWhere<table>
|
|
245
249
|
}
|
|
246
250
|
|
|
251
|
+
/**
|
|
252
|
+
* Options for updating a single row.
|
|
253
|
+
*/
|
|
247
254
|
export type UpdateOptions<
|
|
248
255
|
table extends AnyTable,
|
|
249
256
|
relations extends RelationMapForSourceName<TableName<table>> = {},
|
|
@@ -252,6 +259,9 @@ export type UpdateOptions<
|
|
|
252
259
|
with?: relations
|
|
253
260
|
}
|
|
254
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Options for updating many rows.
|
|
264
|
+
*/
|
|
255
265
|
export type UpdateManyOptions<table extends AnyTable> = {
|
|
256
266
|
where: SingleTableWhere<table>
|
|
257
267
|
orderBy?: OrderByInput<table>
|
|
@@ -260,6 +270,9 @@ export type UpdateManyOptions<table extends AnyTable> = {
|
|
|
260
270
|
touch?: boolean
|
|
261
271
|
}
|
|
262
272
|
|
|
273
|
+
/**
|
|
274
|
+
* Options for deleting many rows.
|
|
275
|
+
*/
|
|
263
276
|
export type DeleteManyOptions<table extends AnyTable> = {
|
|
264
277
|
where: SingleTableWhere<table>
|
|
265
278
|
orderBy?: OrderByInput<table>
|
|
@@ -267,15 +280,24 @@ export type DeleteManyOptions<table extends AnyTable> = {
|
|
|
267
280
|
offset?: number
|
|
268
281
|
}
|
|
269
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Options for counting rows.
|
|
285
|
+
*/
|
|
270
286
|
export type CountOptions<table extends AnyTable> = {
|
|
271
287
|
where?: SingleTableWhere<table>
|
|
272
288
|
}
|
|
273
289
|
|
|
290
|
+
/**
|
|
291
|
+
* Options for create operations that return only write metadata.
|
|
292
|
+
*/
|
|
274
293
|
export type CreateResultOptions = {
|
|
275
294
|
touch?: boolean
|
|
276
295
|
returnRow?: false
|
|
277
296
|
}
|
|
278
297
|
|
|
298
|
+
/**
|
|
299
|
+
* Options for create operations that return the inserted row.
|
|
300
|
+
*/
|
|
279
301
|
export type CreateRowOptions<
|
|
280
302
|
table extends AnyTable,
|
|
281
303
|
relations extends RelationMapForSourceName<TableName<table>> = {},
|
|
@@ -285,99 +307,64 @@ export type CreateRowOptions<
|
|
|
285
307
|
returnRow: true
|
|
286
308
|
}
|
|
287
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Options for bulk-create operations that return only write metadata.
|
|
312
|
+
*/
|
|
288
313
|
export type CreateManyResultOptions = {
|
|
289
314
|
touch?: boolean
|
|
290
315
|
returnRows?: false
|
|
291
316
|
}
|
|
292
317
|
|
|
318
|
+
/**
|
|
319
|
+
* Options for bulk-create operations that return inserted rows.
|
|
320
|
+
*/
|
|
293
321
|
export type CreateManyRowsOptions = {
|
|
294
322
|
touch?: boolean
|
|
295
323
|
returnRows: true
|
|
296
324
|
}
|
|
297
325
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
values: Partial<TableRow<table>>,
|
|
310
|
-
options: CreateRowOptions<table, relations>,
|
|
311
|
-
): Promise<TableRowWith<table, LoadedRelationMap<relations>>>
|
|
312
|
-
createMany<table extends AnyTable>(
|
|
313
|
-
table: table,
|
|
314
|
-
values: Array<Partial<TableRow<table>>>,
|
|
315
|
-
options?: CreateManyResultOptions,
|
|
316
|
-
): Promise<WriteResult>
|
|
317
|
-
createMany<table extends AnyTable>(
|
|
318
|
-
table: table,
|
|
319
|
-
values: Array<Partial<TableRow<table>>>,
|
|
320
|
-
options: CreateManyRowsOptions,
|
|
321
|
-
): Promise<TableRow<table>[]>
|
|
322
|
-
find<table extends AnyTable, relations extends RelationMapForSourceName<TableName<table>> = {}>(
|
|
323
|
-
table: table,
|
|
324
|
-
value: PrimaryKeyInput<table>,
|
|
325
|
-
options?: { with?: relations },
|
|
326
|
-
): Promise<TableRowWith<table, LoadedRelationMap<relations>> | null>
|
|
327
|
-
findOne<
|
|
328
|
-
table extends AnyTable,
|
|
329
|
-
relations extends RelationMapForSourceName<TableName<table>> = {},
|
|
330
|
-
>(
|
|
331
|
-
table: table,
|
|
332
|
-
options: FindOneOptions<table, relations>,
|
|
333
|
-
): Promise<TableRowWith<table, LoadedRelationMap<relations>> | null>
|
|
334
|
-
findMany<
|
|
335
|
-
table extends AnyTable,
|
|
336
|
-
relations extends RelationMapForSourceName<TableName<table>> = {},
|
|
337
|
-
>(
|
|
338
|
-
table: table,
|
|
339
|
-
options?: FindManyOptions<table, relations>,
|
|
340
|
-
): Promise<Array<TableRowWith<table, LoadedRelationMap<relations>>>>
|
|
341
|
-
count<table extends AnyTable>(table: table, options?: CountOptions<table>): Promise<number>
|
|
342
|
-
update<table extends AnyTable, relations extends RelationMapForSourceName<TableName<table>> = {}>(
|
|
343
|
-
table: table,
|
|
344
|
-
value: PrimaryKeyInput<table>,
|
|
345
|
-
changes: Partial<TableRow<table>>,
|
|
346
|
-
options?: UpdateOptions<table, relations>,
|
|
347
|
-
): Promise<TableRowWith<table, LoadedRelationMap<relations>>>
|
|
348
|
-
updateMany<table extends AnyTable>(
|
|
349
|
-
table: table,
|
|
350
|
-
changes: Partial<TableRow<table>>,
|
|
351
|
-
options: UpdateManyOptions<table>,
|
|
352
|
-
): Promise<WriteResult>
|
|
353
|
-
delete<table extends AnyTable>(table: table, value: PrimaryKeyInput<table>): Promise<boolean>
|
|
354
|
-
deleteMany<table extends AnyTable>(
|
|
355
|
-
table: table,
|
|
356
|
-
options: DeleteManyOptions<table>,
|
|
357
|
-
): Promise<WriteResult>
|
|
358
|
-
exec(statement: string | SqlStatement, values?: unknown[]): Promise<AdapterResult>
|
|
359
|
-
transaction<result>(
|
|
360
|
-
callback: (database: Database) => Promise<result>,
|
|
361
|
-
options?: TransactionOptions,
|
|
362
|
-
): Promise<result>
|
|
326
|
+
type SavepointCounter = {
|
|
327
|
+
value: number
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
type DatabaseOptions = {
|
|
331
|
+
now?: () => unknown
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
type DatabaseInternalState = {
|
|
335
|
+
token?: TransactionToken
|
|
336
|
+
savepointCounter: SavepointCounter
|
|
363
337
|
}
|
|
364
338
|
|
|
365
|
-
|
|
339
|
+
const createInternalDatabase = Symbol('createInternalDatabase')
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* High-level database runtime used to build and execute data manipulation operations.
|
|
343
|
+
*
|
|
344
|
+
* Create instances directly with `new Database(adapter, options)` or use
|
|
345
|
+
* `createDatabase(adapter, options)` as a thin wrapper.
|
|
346
|
+
*/
|
|
347
|
+
export class Database implements QueryExecutionContext {
|
|
366
348
|
#adapter: DatabaseAdapter
|
|
367
349
|
#token?: TransactionToken
|
|
368
350
|
#now: () => unknown
|
|
369
351
|
#savepointCounter: SavepointCounter
|
|
370
352
|
|
|
371
|
-
constructor(
|
|
372
|
-
adapter
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
353
|
+
constructor(adapter: DatabaseAdapter, options?: DatabaseOptions) {
|
|
354
|
+
this.#adapter = adapter
|
|
355
|
+
this.#now = options?.now ?? defaultNow
|
|
356
|
+
this.#savepointCounter = { value: 0 }
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
static [createInternalDatabase](
|
|
360
|
+
adapter: DatabaseAdapter,
|
|
361
|
+
options: DatabaseOptions | undefined,
|
|
362
|
+
internal: DatabaseInternalState,
|
|
363
|
+
): Database {
|
|
364
|
+
let database = new Database(adapter, options)
|
|
365
|
+
database.#token = internal.token
|
|
366
|
+
database.#savepointCounter = internal.savepointCounter
|
|
367
|
+
return database
|
|
381
368
|
}
|
|
382
369
|
|
|
383
370
|
get adapter(): DatabaseAdapter {
|
|
@@ -388,14 +375,27 @@ class DatabaseRuntime implements Database {
|
|
|
388
375
|
return this.#now()
|
|
389
376
|
}
|
|
390
377
|
|
|
391
|
-
query
|
|
378
|
+
query<
|
|
392
379
|
tableName extends string,
|
|
393
380
|
row extends Record<string, unknown>,
|
|
394
381
|
primaryKey extends readonly (keyof row & string)[],
|
|
395
382
|
>(
|
|
396
383
|
table: QueryTableInput<tableName, row, primaryKey>,
|
|
397
|
-
):
|
|
398
|
-
|
|
384
|
+
): QueryObject<
|
|
385
|
+
QueryTableInput<tableName, row, primaryKey>,
|
|
386
|
+
Pretty<QueryColumnTypeMapFromRow<tableName, row>>,
|
|
387
|
+
row,
|
|
388
|
+
{},
|
|
389
|
+
BoundQueryPhase<'all'>
|
|
390
|
+
> {
|
|
391
|
+
return createQuery(table)[bindQueryRuntime](this) as QueryObject<
|
|
392
|
+
QueryTableInput<tableName, row, primaryKey>,
|
|
393
|
+
Pretty<QueryColumnTypeMapFromRow<tableName, row>>,
|
|
394
|
+
row,
|
|
395
|
+
{},
|
|
396
|
+
BoundQueryPhase<'all'>
|
|
397
|
+
>
|
|
398
|
+
}
|
|
399
399
|
|
|
400
400
|
create<table extends AnyTable>(
|
|
401
401
|
table: table,
|
|
@@ -427,7 +427,7 @@ class DatabaseRuntime implements Database {
|
|
|
427
427
|
let result = (await query.insert(values, {
|
|
428
428
|
returning: '*',
|
|
429
429
|
touch,
|
|
430
|
-
})) as
|
|
430
|
+
})) as { row: TableRow<table> | null }
|
|
431
431
|
let row = result.row
|
|
432
432
|
|
|
433
433
|
if (!row) {
|
|
@@ -494,7 +494,7 @@ class DatabaseRuntime implements Database {
|
|
|
494
494
|
let result = (await query.insertMany(values, {
|
|
495
495
|
returning: '*',
|
|
496
496
|
touch: options.touch,
|
|
497
|
-
})) as
|
|
497
|
+
})) as { rows: TableRow<table>[] }
|
|
498
498
|
|
|
499
499
|
return result.rows
|
|
500
500
|
}
|
|
@@ -521,16 +521,16 @@ class DatabaseRuntime implements Database {
|
|
|
521
521
|
let query: QueryForTable<table> = this.query(asQueryTableInput(table))
|
|
522
522
|
|
|
523
523
|
if (options?.with) {
|
|
524
|
-
return query
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
) as Promise<TableRowWith<table, LoadedRelationMap<relations>> | null>
|
|
524
|
+
return query.with(options.with).find(value as any) as Promise<TableRowWith<
|
|
525
|
+
table,
|
|
526
|
+
LoadedRelationMap<relations>
|
|
527
|
+
> | null>
|
|
529
528
|
}
|
|
530
529
|
|
|
531
|
-
return query.find(
|
|
532
|
-
|
|
533
|
-
|
|
530
|
+
return query.find(value as any) as Promise<TableRowWith<
|
|
531
|
+
table,
|
|
532
|
+
LoadedRelationMap<relations>
|
|
533
|
+
> | null>
|
|
534
534
|
}
|
|
535
535
|
|
|
536
536
|
async findOne<
|
|
@@ -620,7 +620,7 @@ class DatabaseRuntime implements Database {
|
|
|
620
620
|
let updateResult = (await this.query(asQueryTableInput(table)).where(where).update(changes, {
|
|
621
621
|
touch: options?.touch,
|
|
622
622
|
returning: '*',
|
|
623
|
-
})) as
|
|
623
|
+
})) as { rows: TableRow<table>[] }
|
|
624
624
|
let updatedRow = updateResult.rows[0]
|
|
625
625
|
|
|
626
626
|
if (!updatedRow) {
|
|
@@ -718,13 +718,24 @@ class DatabaseRuntime implements Database {
|
|
|
718
718
|
return toWriteResult(result)
|
|
719
719
|
}
|
|
720
720
|
|
|
721
|
-
async exec(statement: string | SqlStatement, values
|
|
722
|
-
|
|
721
|
+
async exec(statement: string | SqlStatement, values?: unknown[]): Promise<DataManipulationResult>
|
|
722
|
+
async exec<input extends AnyQuery>(input: input): Promise<QueryExecutionResult<input>>
|
|
723
|
+
async exec<input extends AnyQuery>(
|
|
724
|
+
statementOrInput: string | SqlStatement | input,
|
|
725
|
+
values: unknown[] = [],
|
|
726
|
+
): Promise<DataManipulationResult | QueryExecutionResult<input>> {
|
|
727
|
+
if (typeof statementOrInput === 'string' || isSqlStatement(statementOrInput)) {
|
|
728
|
+
let sqlStatement = isSqlStatement(statementOrInput)
|
|
729
|
+
? statementOrInput
|
|
730
|
+
: rawSql(statementOrInput, values)
|
|
731
|
+
|
|
732
|
+
return this[executeOperation]({
|
|
733
|
+
kind: 'raw',
|
|
734
|
+
sql: sqlStatement,
|
|
735
|
+
})
|
|
736
|
+
}
|
|
723
737
|
|
|
724
|
-
return this
|
|
725
|
-
kind: 'raw',
|
|
726
|
-
sql: sqlStatement,
|
|
727
|
-
})
|
|
738
|
+
return executeQuery(this, statementOrInput)
|
|
728
739
|
}
|
|
729
740
|
|
|
730
741
|
async transaction<result>(
|
|
@@ -733,12 +744,14 @@ class DatabaseRuntime implements Database {
|
|
|
733
744
|
): Promise<result> {
|
|
734
745
|
if (!this.#token) {
|
|
735
746
|
let token = await this.#adapter.beginTransaction(options)
|
|
736
|
-
let tx =
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
747
|
+
let tx = Database[createInternalDatabase](
|
|
748
|
+
this.#adapter,
|
|
749
|
+
{ now: this.#now },
|
|
750
|
+
{
|
|
751
|
+
token,
|
|
752
|
+
savepointCounter: this.#savepointCounter,
|
|
753
|
+
},
|
|
754
|
+
)
|
|
742
755
|
|
|
743
756
|
try {
|
|
744
757
|
let result = await callback(tx)
|
|
@@ -770,10 +783,10 @@ class DatabaseRuntime implements Database {
|
|
|
770
783
|
}
|
|
771
784
|
}
|
|
772
785
|
|
|
773
|
-
async [
|
|
786
|
+
async [executeOperation](operation: DataManipulationOperation): Promise<DataManipulationResult> {
|
|
774
787
|
try {
|
|
775
788
|
return await this.#adapter.execute({
|
|
776
|
-
|
|
789
|
+
operation,
|
|
777
790
|
transaction: this.#token,
|
|
778
791
|
})
|
|
779
792
|
} catch (error) {
|
|
@@ -781,7 +794,7 @@ class DatabaseRuntime implements Database {
|
|
|
781
794
|
cause: error,
|
|
782
795
|
metadata: {
|
|
783
796
|
dialect: this.#adapter.dialect,
|
|
784
|
-
|
|
797
|
+
operationKind: operation.kind,
|
|
785
798
|
},
|
|
786
799
|
})
|
|
787
800
|
}
|
|
@@ -790,1669 +803,54 @@ class DatabaseRuntime implements Database {
|
|
|
790
803
|
|
|
791
804
|
/**
|
|
792
805
|
* Creates a database runtime from an adapter.
|
|
806
|
+
* Thin wrapper around `new Database(adapter, options)`.
|
|
793
807
|
* @param adapter Adapter implementation responsible for SQL execution.
|
|
794
808
|
* @param options Optional runtime options.
|
|
795
809
|
* @param options.now Clock function used for auto-managed timestamps.
|
|
796
|
-
* @returns A
|
|
810
|
+
* @returns A {@link Database} API instance.
|
|
811
|
+
* @example
|
|
812
|
+
* ```ts
|
|
813
|
+
* import { column as c, createDatabase, table } from 'remix/data-table'
|
|
814
|
+
*
|
|
815
|
+
* let users = table({
|
|
816
|
+
* name: 'users',
|
|
817
|
+
* columns: {
|
|
818
|
+
* id: c.integer(),
|
|
819
|
+
* email: c.varchar(255),
|
|
820
|
+
* },
|
|
821
|
+
* })
|
|
822
|
+
*
|
|
823
|
+
* let db = createDatabase(adapter)
|
|
824
|
+
* let rows = await db.query(users).where({ id: 1 }).all()
|
|
825
|
+
* ```
|
|
797
826
|
*/
|
|
798
827
|
export function createDatabase(
|
|
799
828
|
adapter: DatabaseAdapter,
|
|
800
829
|
options?: { now?: () => unknown },
|
|
801
830
|
): Database {
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
return new DatabaseRuntime({
|
|
805
|
-
adapter,
|
|
806
|
-
token: undefined,
|
|
807
|
-
now,
|
|
808
|
-
savepointCounter: { value: 0 },
|
|
809
|
-
})
|
|
831
|
+
return new Database(adapter, options)
|
|
810
832
|
}
|
|
811
833
|
|
|
812
834
|
/**
|
|
813
|
-
*
|
|
835
|
+
* Creates a database runtime bound to an existing adapter transaction token.
|
|
836
|
+
* This is an internal helper used by the migration runner.
|
|
837
|
+
* @param adapter Adapter implementation responsible for SQL execution.
|
|
838
|
+
* @param token Active adapter transaction token.
|
|
839
|
+
* @param options Optional runtime options.
|
|
840
|
+
* @param options.now Clock function used for auto-managed timestamps.
|
|
841
|
+
* @returns A {@link Database} API instance bound to the provided transaction.
|
|
814
842
|
*/
|
|
815
|
-
export
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
constructor(database: DatabaseRuntime, table: AnyTable, state: QueryState) {
|
|
827
|
-
this.#database = database
|
|
828
|
-
this.#table = table
|
|
829
|
-
this.#state = state
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
/**
|
|
833
|
-
* Narrows selected columns, optionally with aliases.
|
|
834
|
-
*/
|
|
835
|
-
select<selection extends (keyof row & string)[]>(
|
|
836
|
-
...columns: selection
|
|
837
|
-
): QueryBuilder<columnTypes, Pick<row, selection[number]>, loaded, tableName, primaryKey>
|
|
838
|
-
select<selection extends Record<string, QueryColumnInput<columnTypes>>>(
|
|
839
|
-
selection: selection,
|
|
840
|
-
): QueryBuilder<
|
|
841
|
-
columnTypes,
|
|
842
|
-
SelectedAliasRow<columnTypes, selection>,
|
|
843
|
-
loaded,
|
|
844
|
-
tableName,
|
|
845
|
-
primaryKey
|
|
846
|
-
>
|
|
847
|
-
select(
|
|
848
|
-
...input: [Record<string, QueryColumnInput<columnTypes>>] | (keyof row & string)[]
|
|
849
|
-
): QueryBuilder<columnTypes, any, loaded, tableName, primaryKey> {
|
|
850
|
-
if (
|
|
851
|
-
input.length === 1 &&
|
|
852
|
-
typeof input[0] === 'object' &&
|
|
853
|
-
input[0] !== null &&
|
|
854
|
-
!Array.isArray(input[0])
|
|
855
|
-
) {
|
|
856
|
-
let selection = input[0] as Record<string, QueryColumnInput<columnTypes>>
|
|
857
|
-
let aliases = Object.keys(selection)
|
|
858
|
-
let select = aliases.map((alias) => ({
|
|
859
|
-
column: normalizeColumnInput(selection[alias]),
|
|
860
|
-
alias,
|
|
861
|
-
}))
|
|
862
|
-
|
|
863
|
-
return this.#clone({ select }) as QueryBuilder<
|
|
864
|
-
columnTypes,
|
|
865
|
-
any,
|
|
866
|
-
loaded,
|
|
867
|
-
tableName,
|
|
868
|
-
primaryKey
|
|
869
|
-
>
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
let columns = input as (keyof row & string)[]
|
|
873
|
-
|
|
874
|
-
return this.#clone({
|
|
875
|
-
select: columns.map((column) => ({ column, alias: column })),
|
|
876
|
-
}) as QueryBuilder<columnTypes, any, loaded, tableName, primaryKey>
|
|
877
|
-
}
|
|
878
|
-
|
|
879
|
-
/**
|
|
880
|
-
* Toggles `distinct` selection.
|
|
881
|
-
* @param value When `true`, eliminates duplicate rows.
|
|
882
|
-
* @returns A cloned query builder with updated distinct state.
|
|
883
|
-
*/
|
|
884
|
-
distinct(value = true): QueryBuilder<columnTypes, row, loaded, tableName, primaryKey> {
|
|
885
|
-
return this.#clone({ distinct: value })
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
/**
|
|
889
|
-
* Adds a where predicate.
|
|
890
|
-
* @param input Predicate expression or column-value shorthand.
|
|
891
|
-
* @returns A cloned query builder with the appended where predicate.
|
|
892
|
-
*/
|
|
893
|
-
where(
|
|
894
|
-
input: WhereInput<QueryColumns<columnTypes>>,
|
|
895
|
-
): QueryBuilder<columnTypes, row, loaded, tableName, primaryKey> {
|
|
896
|
-
let predicate = normalizeWhereInput(input)
|
|
897
|
-
let normalizedPredicate = normalizePredicateValues(
|
|
898
|
-
predicate,
|
|
899
|
-
createPredicateColumnResolver([this.#table, ...this.#state.joins.map((join) => join.table)]),
|
|
900
|
-
)
|
|
901
|
-
|
|
902
|
-
return this.#clone({
|
|
903
|
-
where: [...this.#state.where, normalizedPredicate],
|
|
904
|
-
})
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
/**
|
|
908
|
-
* Adds a having predicate.
|
|
909
|
-
* @param input Predicate expression or aggregate filter shorthand.
|
|
910
|
-
* @returns A cloned query builder with the appended having predicate.
|
|
911
|
-
*/
|
|
912
|
-
having(
|
|
913
|
-
input: WhereInput<QueryColumns<columnTypes>>,
|
|
914
|
-
): QueryBuilder<columnTypes, row, loaded, tableName, primaryKey> {
|
|
915
|
-
let predicate = normalizeWhereInput(input)
|
|
916
|
-
let normalizedPredicate = normalizePredicateValues(
|
|
917
|
-
predicate,
|
|
918
|
-
createPredicateColumnResolver([this.#table, ...this.#state.joins.map((join) => join.table)]),
|
|
919
|
-
)
|
|
920
|
-
|
|
921
|
-
return this.#clone({
|
|
922
|
-
having: [...this.#state.having, normalizedPredicate],
|
|
923
|
-
})
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
/**
|
|
927
|
-
* Adds a join clause.
|
|
928
|
-
* @param target Target table to join.
|
|
929
|
-
* @param on Join predicate.
|
|
930
|
-
* @param type Join type.
|
|
931
|
-
* @returns A query builder whose column map includes joined table columns.
|
|
932
|
-
*/
|
|
933
|
-
join<target extends AnyTable>(
|
|
934
|
-
target: target,
|
|
935
|
-
on: Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>,
|
|
936
|
-
type: JoinType = 'inner',
|
|
937
|
-
): QueryBuilder<
|
|
938
|
-
MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
|
|
939
|
-
row,
|
|
940
|
-
loaded,
|
|
941
|
-
tableName,
|
|
942
|
-
primaryKey
|
|
943
|
-
> {
|
|
944
|
-
let normalizedOn = normalizePredicateValues(
|
|
945
|
-
on,
|
|
946
|
-
createPredicateColumnResolver([
|
|
947
|
-
this.#table,
|
|
948
|
-
...this.#state.joins.map((join) => join.table),
|
|
949
|
-
target,
|
|
950
|
-
]),
|
|
951
|
-
) as Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>
|
|
952
|
-
|
|
953
|
-
return new QueryBuilder(this.#database, this.#table, {
|
|
954
|
-
select: cloneSelection(this.#state.select),
|
|
955
|
-
distinct: this.#state.distinct,
|
|
956
|
-
joins: [...this.#state.joins, { type, table: target, on: normalizedOn }],
|
|
957
|
-
where: [...this.#state.where],
|
|
958
|
-
groupBy: [...this.#state.groupBy],
|
|
959
|
-
having: [...this.#state.having],
|
|
960
|
-
orderBy: [...this.#state.orderBy],
|
|
961
|
-
limit: this.#state.limit,
|
|
962
|
-
offset: this.#state.offset,
|
|
963
|
-
with: { ...this.#state.with },
|
|
964
|
-
}) as QueryBuilder<
|
|
965
|
-
MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
|
|
966
|
-
row,
|
|
967
|
-
loaded,
|
|
968
|
-
tableName,
|
|
969
|
-
primaryKey
|
|
970
|
-
>
|
|
971
|
-
}
|
|
972
|
-
|
|
973
|
-
/**
|
|
974
|
-
* Adds a left join clause.
|
|
975
|
-
* @param target Target table to join.
|
|
976
|
-
* @param on Join predicate.
|
|
977
|
-
* @returns A query builder whose column map includes joined table columns.
|
|
978
|
-
*/
|
|
979
|
-
leftJoin<target extends AnyTable>(
|
|
980
|
-
target: target,
|
|
981
|
-
on: Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>,
|
|
982
|
-
): QueryBuilder<
|
|
983
|
-
MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
|
|
984
|
-
row,
|
|
985
|
-
loaded,
|
|
986
|
-
tableName,
|
|
987
|
-
primaryKey
|
|
988
|
-
> {
|
|
989
|
-
return this.join(target, on, 'left')
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
/**
|
|
993
|
-
* Adds a right join clause.
|
|
994
|
-
* @param target Target table to join.
|
|
995
|
-
* @param on Join predicate.
|
|
996
|
-
* @returns A query builder whose column map includes joined table columns.
|
|
997
|
-
*/
|
|
998
|
-
rightJoin<target extends AnyTable>(
|
|
999
|
-
target: target,
|
|
1000
|
-
on: Predicate<QueryColumns<columnTypes> | QueryColumnName<target>>,
|
|
1001
|
-
): QueryBuilder<
|
|
1002
|
-
MergeColumnTypeMaps<columnTypes, QueryColumnTypeMap<target>>,
|
|
1003
|
-
row,
|
|
1004
|
-
loaded,
|
|
1005
|
-
tableName,
|
|
1006
|
-
primaryKey
|
|
1007
|
-
> {
|
|
1008
|
-
return this.join(target, on, 'right')
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
/**
|
|
1012
|
-
* Appends an order-by clause.
|
|
1013
|
-
* @param column Column to sort by.
|
|
1014
|
-
* @param direction Sort direction.
|
|
1015
|
-
* @returns A cloned query builder with the appended order-by clause.
|
|
1016
|
-
*/
|
|
1017
|
-
orderBy(
|
|
1018
|
-
column: QueryColumnInput<columnTypes>,
|
|
1019
|
-
direction: OrderDirection = 'asc',
|
|
1020
|
-
): QueryBuilder<columnTypes, row, loaded, tableName, primaryKey> {
|
|
1021
|
-
return this.#clone({
|
|
1022
|
-
orderBy: [...this.#state.orderBy, { column: normalizeColumnInput(column), direction }],
|
|
1023
|
-
})
|
|
1024
|
-
}
|
|
1025
|
-
|
|
1026
|
-
/**
|
|
1027
|
-
* Appends group-by columns.
|
|
1028
|
-
* @param columns Columns to include in the grouping set.
|
|
1029
|
-
* @returns A cloned query builder with appended group-by columns.
|
|
1030
|
-
*/
|
|
1031
|
-
groupBy(
|
|
1032
|
-
...columns: QueryColumnInput<columnTypes>[]
|
|
1033
|
-
): QueryBuilder<columnTypes, row, loaded, tableName, primaryKey> {
|
|
1034
|
-
return this.#clone({
|
|
1035
|
-
groupBy: [...this.#state.groupBy, ...columns.map((column) => normalizeColumnInput(column))],
|
|
1036
|
-
})
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
/**
|
|
1040
|
-
* Limits returned rows.
|
|
1041
|
-
* @param value Maximum number of rows to return.
|
|
1042
|
-
* @returns A cloned query builder with a row limit.
|
|
1043
|
-
*/
|
|
1044
|
-
limit(value: number): QueryBuilder<columnTypes, row, loaded, tableName, primaryKey> {
|
|
1045
|
-
return this.#clone({ limit: value })
|
|
1046
|
-
}
|
|
1047
|
-
|
|
1048
|
-
/**
|
|
1049
|
-
* Skips returned rows.
|
|
1050
|
-
* @param value Number of rows to skip.
|
|
1051
|
-
* @returns A cloned query builder with a row offset.
|
|
1052
|
-
*/
|
|
1053
|
-
offset(value: number): QueryBuilder<columnTypes, row, loaded, tableName, primaryKey> {
|
|
1054
|
-
return this.#clone({ offset: value })
|
|
1055
|
-
}
|
|
1056
|
-
|
|
1057
|
-
/**
|
|
1058
|
-
* Configures eager-loaded relations.
|
|
1059
|
-
* @param relations Relation map describing nested eager-load behavior.
|
|
1060
|
-
* @returns A cloned query builder with relation loading configuration.
|
|
1061
|
-
*/
|
|
1062
|
-
with<relations extends RelationMapForSourceName<tableName>>(
|
|
1063
|
-
relations: relations,
|
|
1064
|
-
): QueryBuilder<columnTypes, row, loaded & LoadedRelationMap<relations>, tableName, primaryKey> {
|
|
1065
|
-
return this.#clone({
|
|
1066
|
-
with: {
|
|
1067
|
-
...this.#state.with,
|
|
1068
|
-
...relations,
|
|
1069
|
-
},
|
|
1070
|
-
}) as QueryBuilder<
|
|
1071
|
-
columnTypes,
|
|
1072
|
-
row,
|
|
1073
|
-
loaded & LoadedRelationMap<relations>,
|
|
1074
|
-
tableName,
|
|
1075
|
-
primaryKey
|
|
1076
|
-
>
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
/**
|
|
1080
|
-
* Executes the query and returns all rows.
|
|
1081
|
-
* @returns All matching rows with requested eager-loaded relations.
|
|
1082
|
-
*/
|
|
1083
|
-
async all(): Promise<Array<row & loaded>> {
|
|
1084
|
-
let statement = this.#toSelectStatement()
|
|
1085
|
-
let result = await this.#database[executeStatement](statement)
|
|
1086
|
-
let rows = normalizeRows(result.rows)
|
|
1087
|
-
|
|
1088
|
-
if (Object.keys(this.#state.with).length === 0) {
|
|
1089
|
-
return rows as Array<row & loaded>
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
let rowsWithRelations = await loadRelationsForRows(
|
|
1093
|
-
this.#database,
|
|
1094
|
-
this.#table,
|
|
1095
|
-
rows,
|
|
1096
|
-
this.#state.with,
|
|
1097
|
-
)
|
|
1098
|
-
return rowsWithRelations as Array<row & loaded>
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
/**
|
|
1102
|
-
* Executes the query and returns the first row.
|
|
1103
|
-
* @returns The first matching row, or `null` when no rows match.
|
|
1104
|
-
*/
|
|
1105
|
-
async first(): Promise<(row & loaded) | null> {
|
|
1106
|
-
let rows = await this.limit(1).all()
|
|
1107
|
-
return rows[0] ?? null
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
/**
|
|
1111
|
-
* Loads a single row by primary key.
|
|
1112
|
-
* @param value Primary-key value or composite-key object.
|
|
1113
|
-
* @returns The matching row, or `null` when no row exists.
|
|
1114
|
-
*/
|
|
1115
|
-
async find(value: PrimaryKeyInputForRow<row, primaryKey>): Promise<(row & loaded) | null> {
|
|
1116
|
-
let where = getPrimaryKeyObject(this.#table, value as any)
|
|
1117
|
-
return this.where(where as WhereInput<QueryColumns<columnTypes>>).first()
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
/**
|
|
1121
|
-
* Executes a count query.
|
|
1122
|
-
* @returns Number of rows that match the current query scope.
|
|
1123
|
-
*/
|
|
1124
|
-
async count(): Promise<number> {
|
|
1125
|
-
let statement: CountStatement<AnyTable> = {
|
|
1126
|
-
kind: 'count',
|
|
1127
|
-
table: this.#table,
|
|
1128
|
-
joins: [...this.#state.joins],
|
|
1129
|
-
where: [...this.#state.where],
|
|
1130
|
-
groupBy: [...this.#state.groupBy],
|
|
1131
|
-
having: [...this.#state.having],
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
let result = await this.#database[executeStatement](statement)
|
|
1135
|
-
|
|
1136
|
-
if (result.rows && result.rows[0] && typeof result.rows[0].count === 'number') {
|
|
1137
|
-
return result.rows[0].count as number
|
|
1138
|
-
}
|
|
1139
|
-
|
|
1140
|
-
if (result.rows) {
|
|
1141
|
-
return result.rows.length
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
return 0
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
/**
|
|
1148
|
-
* Executes an existence query.
|
|
1149
|
-
* @returns `true` when at least one row matches the current query scope.
|
|
1150
|
-
*/
|
|
1151
|
-
async exists(): Promise<boolean> {
|
|
1152
|
-
let statement: ExistsStatement<AnyTable> = {
|
|
1153
|
-
kind: 'exists',
|
|
1154
|
-
table: this.#table,
|
|
1155
|
-
joins: [...this.#state.joins],
|
|
1156
|
-
where: [...this.#state.where],
|
|
1157
|
-
groupBy: [...this.#state.groupBy],
|
|
1158
|
-
having: [...this.#state.having],
|
|
1159
|
-
}
|
|
1160
|
-
|
|
1161
|
-
let result = await this.#database[executeStatement](statement)
|
|
1162
|
-
|
|
1163
|
-
if (result.rows && result.rows[0] && typeof result.rows[0].exists === 'boolean') {
|
|
1164
|
-
return result.rows[0].exists as boolean
|
|
1165
|
-
}
|
|
1166
|
-
|
|
1167
|
-
if (result.rows && result.rows[0] && typeof result.rows[0].count === 'number') {
|
|
1168
|
-
return Number(result.rows[0].count) > 0
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
return Boolean(result.rows && result.rows.length > 0)
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
/**
|
|
1175
|
-
* Inserts one row.
|
|
1176
|
-
* @param values Values to insert.
|
|
1177
|
-
* @param options Insert options.
|
|
1178
|
-
* @param options.returning Optional return selection for adapters that support returning.
|
|
1179
|
-
* @param options.touch When `true`, manages timestamp columns automatically.
|
|
1180
|
-
* @returns Insert metadata, and optionally the returned row.
|
|
1181
|
-
*/
|
|
1182
|
-
async insert(
|
|
1183
|
-
values: Partial<row>,
|
|
1184
|
-
options?: { returning?: ReturningInput<row>; touch?: boolean },
|
|
1185
|
-
): Promise<WriteResult | WriteRowResult<row>> {
|
|
1186
|
-
assertWriteState(this.#state, 'insert', {
|
|
1187
|
-
where: false,
|
|
1188
|
-
orderBy: false,
|
|
1189
|
-
limit: false,
|
|
1190
|
-
offset: false,
|
|
1191
|
-
})
|
|
1192
|
-
|
|
1193
|
-
let preparedValues = prepareInsertValues(
|
|
1194
|
-
this.#table,
|
|
1195
|
-
values,
|
|
1196
|
-
this.#database.now(),
|
|
1197
|
-
options?.touch ?? true,
|
|
1198
|
-
)
|
|
1199
|
-
let returning = options?.returning
|
|
1200
|
-
|
|
1201
|
-
assertReturningCapability(this.#database.adapter, 'insert', returning)
|
|
1202
|
-
|
|
1203
|
-
if (returning) {
|
|
1204
|
-
let statement: InsertStatement<AnyTable> = {
|
|
1205
|
-
kind: 'insert',
|
|
1206
|
-
table: this.#table,
|
|
1207
|
-
values: preparedValues,
|
|
1208
|
-
returning: normalizeReturningSelection(returning),
|
|
1209
|
-
}
|
|
1210
|
-
|
|
1211
|
-
let result = await this.#database[executeStatement](statement)
|
|
1212
|
-
let row = (normalizeRows(result.rows)[0] ?? null) as row | null
|
|
1213
|
-
|
|
1214
|
-
return {
|
|
1215
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1216
|
-
insertId: result.insertId,
|
|
1217
|
-
row,
|
|
1218
|
-
}
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
|
-
let statement: InsertStatement<AnyTable> = {
|
|
1222
|
-
kind: 'insert',
|
|
1223
|
-
table: this.#table,
|
|
1224
|
-
values: preparedValues,
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
let result = await this.#database[executeStatement](statement)
|
|
1228
|
-
let metadata: WriteResult = {
|
|
1229
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1230
|
-
insertId: result.insertId,
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
return metadata
|
|
1234
|
-
}
|
|
1235
|
-
|
|
1236
|
-
/**
|
|
1237
|
-
* Inserts many rows.
|
|
1238
|
-
* @param values Values to insert.
|
|
1239
|
-
* @param options Insert options.
|
|
1240
|
-
* @param options.returning Optional return selection for adapters that support returning.
|
|
1241
|
-
* @param options.touch When `true`, manages timestamp columns automatically.
|
|
1242
|
-
* @returns Insert metadata, and optionally the returned rows.
|
|
1243
|
-
*/
|
|
1244
|
-
async insertMany(
|
|
1245
|
-
values: Partial<row>[],
|
|
1246
|
-
options?: { returning?: ReturningInput<row>; touch?: boolean },
|
|
1247
|
-
): Promise<WriteResult | WriteRowsResult<row>> {
|
|
1248
|
-
assertWriteState(this.#state, 'insertMany', {
|
|
1249
|
-
where: false,
|
|
1250
|
-
orderBy: false,
|
|
1251
|
-
limit: false,
|
|
1252
|
-
offset: false,
|
|
1253
|
-
})
|
|
1254
|
-
|
|
1255
|
-
let preparedValues = values.map((value) =>
|
|
1256
|
-
prepareInsertValues(this.#table, value, this.#database.now(), options?.touch ?? true),
|
|
1257
|
-
)
|
|
1258
|
-
|
|
1259
|
-
if (
|
|
1260
|
-
preparedValues.length > 0 &&
|
|
1261
|
-
preparedValues.every((preparedValue) => Object.keys(preparedValue).length === 0)
|
|
1262
|
-
) {
|
|
1263
|
-
throw new DataTableQueryError(
|
|
1264
|
-
'insertMany() requires at least one explicit value across the batch',
|
|
1265
|
-
)
|
|
1266
|
-
}
|
|
1267
|
-
|
|
1268
|
-
let returning = options?.returning
|
|
1269
|
-
|
|
1270
|
-
assertReturningCapability(this.#database.adapter, 'insertMany', returning)
|
|
1271
|
-
|
|
1272
|
-
if (returning) {
|
|
1273
|
-
let statement: InsertManyStatement<AnyTable> = {
|
|
1274
|
-
kind: 'insertMany',
|
|
1275
|
-
table: this.#table,
|
|
1276
|
-
values: preparedValues,
|
|
1277
|
-
returning: normalizeReturningSelection(returning),
|
|
1278
|
-
}
|
|
1279
|
-
|
|
1280
|
-
let result = await this.#database[executeStatement](statement)
|
|
1281
|
-
|
|
1282
|
-
return {
|
|
1283
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1284
|
-
insertId: result.insertId,
|
|
1285
|
-
rows: normalizeRows(result.rows) as row[],
|
|
1286
|
-
}
|
|
1287
|
-
}
|
|
1288
|
-
|
|
1289
|
-
let statement: InsertManyStatement<AnyTable> = {
|
|
1290
|
-
kind: 'insertMany',
|
|
1291
|
-
table: this.#table,
|
|
1292
|
-
values: preparedValues,
|
|
1293
|
-
}
|
|
1294
|
-
|
|
1295
|
-
let result = await this.#database[executeStatement](statement)
|
|
1296
|
-
let metadata: WriteResult = {
|
|
1297
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1298
|
-
insertId: result.insertId,
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
return metadata
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
/**
|
|
1305
|
-
* Updates scoped rows.
|
|
1306
|
-
* @param changes Column changes to apply.
|
|
1307
|
-
* @param options Update options.
|
|
1308
|
-
* @param options.returning Optional return selection for adapters that support returning.
|
|
1309
|
-
* @param options.touch When `true`, updates timestamp columns automatically.
|
|
1310
|
-
* @returns Update metadata, and optionally the returned rows.
|
|
1311
|
-
*/
|
|
1312
|
-
async update(
|
|
1313
|
-
changes: Partial<row>,
|
|
1314
|
-
options?: { returning?: ReturningInput<row>; touch?: boolean },
|
|
1315
|
-
): Promise<WriteResult | WriteRowsResult<row>> {
|
|
1316
|
-
assertWriteState(this.#state, 'update', {
|
|
1317
|
-
where: true,
|
|
1318
|
-
orderBy: true,
|
|
1319
|
-
limit: true,
|
|
1320
|
-
offset: true,
|
|
1321
|
-
})
|
|
1322
|
-
|
|
1323
|
-
let preparedChanges = prepareUpdateValues(
|
|
1324
|
-
this.#table,
|
|
1325
|
-
changes,
|
|
1326
|
-
this.#database.now(),
|
|
1327
|
-
options?.touch ?? true,
|
|
1328
|
-
)
|
|
1329
|
-
let returning = options?.returning
|
|
1330
|
-
assertReturningCapability(this.#database.adapter, 'update', returning)
|
|
1331
|
-
|
|
1332
|
-
if (Object.keys(preparedChanges).length === 0) {
|
|
1333
|
-
throw new DataTableQueryError('update() requires at least one change')
|
|
1334
|
-
}
|
|
1335
|
-
|
|
1336
|
-
if (hasScopedWriteModifiers(this.#state)) {
|
|
1337
|
-
let table = this.#table
|
|
1338
|
-
let queryState = this.#state
|
|
1339
|
-
|
|
1340
|
-
return this.#database.transaction(async (tx: Database) => {
|
|
1341
|
-
let primaryKeys = await loadPrimaryKeyRowsForScope(tx, table, queryState)
|
|
1342
|
-
let primaryKeyPredicate = buildPrimaryKeyPredicate(table, primaryKeys)
|
|
1343
|
-
|
|
1344
|
-
if (!primaryKeyPredicate) {
|
|
1345
|
-
if (!returning) {
|
|
1346
|
-
return {
|
|
1347
|
-
affectedRows: 0,
|
|
1348
|
-
insertId: undefined,
|
|
1349
|
-
}
|
|
1350
|
-
}
|
|
1351
|
-
|
|
1352
|
-
return {
|
|
1353
|
-
affectedRows: 0,
|
|
1354
|
-
insertId: undefined,
|
|
1355
|
-
rows: [],
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
return tx.query(table).where(primaryKeyPredicate).update(changes, options)
|
|
1360
|
-
})
|
|
1361
|
-
}
|
|
1362
|
-
|
|
1363
|
-
let statement: UpdateStatement<AnyTable> = {
|
|
1364
|
-
kind: 'update',
|
|
1365
|
-
table: this.#table,
|
|
1366
|
-
changes: preparedChanges,
|
|
1367
|
-
where: [...this.#state.where],
|
|
1368
|
-
returning: returning ? normalizeReturningSelection(returning) : undefined,
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
let result = await this.#database[executeStatement](statement)
|
|
1372
|
-
|
|
1373
|
-
if (!returning) {
|
|
1374
|
-
return {
|
|
1375
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1376
|
-
insertId: result.insertId,
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
|
|
1380
|
-
return {
|
|
1381
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1382
|
-
insertId: result.insertId,
|
|
1383
|
-
rows: normalizeRows(result.rows) as row[],
|
|
1384
|
-
}
|
|
1385
|
-
}
|
|
1386
|
-
|
|
1387
|
-
/**
|
|
1388
|
-
* Deletes scoped rows.
|
|
1389
|
-
* @param options Delete options.
|
|
1390
|
-
* @param options.returning Optional return selection for adapters that support returning.
|
|
1391
|
-
* @returns Delete metadata, and optionally the returned rows.
|
|
1392
|
-
*/
|
|
1393
|
-
async delete(options?: {
|
|
1394
|
-
returning?: ReturningInput<row>
|
|
1395
|
-
}): Promise<WriteResult | WriteRowsResult<row>> {
|
|
1396
|
-
assertWriteState(this.#state, 'delete', {
|
|
1397
|
-
where: true,
|
|
1398
|
-
orderBy: true,
|
|
1399
|
-
limit: true,
|
|
1400
|
-
offset: true,
|
|
1401
|
-
})
|
|
1402
|
-
|
|
1403
|
-
let returning = options?.returning
|
|
1404
|
-
assertReturningCapability(this.#database.adapter, 'delete', returning)
|
|
1405
|
-
|
|
1406
|
-
if (hasScopedWriteModifiers(this.#state)) {
|
|
1407
|
-
let table = this.#table
|
|
1408
|
-
let queryState = this.#state
|
|
1409
|
-
|
|
1410
|
-
return this.#database.transaction(async (tx: Database) => {
|
|
1411
|
-
let primaryKeys = await loadPrimaryKeyRowsForScope(tx, table, queryState)
|
|
1412
|
-
let primaryKeyPredicate = buildPrimaryKeyPredicate(table, primaryKeys)
|
|
1413
|
-
|
|
1414
|
-
if (!primaryKeyPredicate) {
|
|
1415
|
-
if (!returning) {
|
|
1416
|
-
return {
|
|
1417
|
-
affectedRows: 0,
|
|
1418
|
-
insertId: undefined,
|
|
1419
|
-
}
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
return {
|
|
1423
|
-
affectedRows: 0,
|
|
1424
|
-
insertId: undefined,
|
|
1425
|
-
rows: [],
|
|
1426
|
-
}
|
|
1427
|
-
}
|
|
1428
|
-
|
|
1429
|
-
return tx.query(table).where(primaryKeyPredicate).delete(options)
|
|
1430
|
-
})
|
|
1431
|
-
}
|
|
1432
|
-
|
|
1433
|
-
let statement: DeleteStatement<AnyTable> = {
|
|
1434
|
-
kind: 'delete',
|
|
1435
|
-
table: this.#table,
|
|
1436
|
-
where: [...this.#state.where],
|
|
1437
|
-
returning: returning ? normalizeReturningSelection(returning) : undefined,
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
let result = await this.#database[executeStatement](statement)
|
|
1441
|
-
|
|
1442
|
-
if (!returning) {
|
|
1443
|
-
return {
|
|
1444
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1445
|
-
insertId: result.insertId,
|
|
1446
|
-
}
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
return {
|
|
1450
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1451
|
-
insertId: result.insertId,
|
|
1452
|
-
rows: normalizeRows(result.rows) as row[],
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
/**
|
|
1457
|
-
* Performs an upsert operation.
|
|
1458
|
-
* @param values Values to insert.
|
|
1459
|
-
* @param options Upsert options.
|
|
1460
|
-
* @param options.returning Optional return selection for adapters that support returning.
|
|
1461
|
-
* @param options.touch When `true`, manages timestamp columns automatically.
|
|
1462
|
-
* @param options.conflictTarget Conflict target columns for adapters that require them.
|
|
1463
|
-
* @param options.update Optional update payload used when a conflict occurs.
|
|
1464
|
-
* @returns Upsert metadata, and optionally the returned row.
|
|
1465
|
-
*/
|
|
1466
|
-
async upsert(
|
|
1467
|
-
values: Partial<row>,
|
|
1468
|
-
options?: {
|
|
1469
|
-
returning?: ReturningInput<row>
|
|
1470
|
-
touch?: boolean
|
|
1471
|
-
conflictTarget?: (keyof row & string)[]
|
|
1472
|
-
update?: Partial<row>
|
|
1473
|
-
},
|
|
1474
|
-
): Promise<WriteResult | WriteRowResult<row>> {
|
|
1475
|
-
assertWriteState(this.#state, 'upsert', {
|
|
1476
|
-
where: false,
|
|
1477
|
-
orderBy: false,
|
|
1478
|
-
limit: false,
|
|
1479
|
-
offset: false,
|
|
1480
|
-
})
|
|
1481
|
-
|
|
1482
|
-
if (!this.#database.adapter.capabilities.upsert) {
|
|
1483
|
-
throw new DataTableQueryError('Adapter does not support upsert')
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
let preparedValues = prepareInsertValues(
|
|
1487
|
-
this.#table,
|
|
1488
|
-
values,
|
|
1489
|
-
this.#database.now(),
|
|
1490
|
-
options?.touch ?? true,
|
|
1491
|
-
)
|
|
1492
|
-
let updateChanges = options?.update
|
|
1493
|
-
? prepareUpdateValues(
|
|
1494
|
-
this.#table,
|
|
1495
|
-
options.update,
|
|
1496
|
-
this.#database.now(),
|
|
1497
|
-
options?.touch ?? true,
|
|
1498
|
-
)
|
|
1499
|
-
: undefined
|
|
1500
|
-
let returning = options?.returning
|
|
1501
|
-
assertReturningCapability(this.#database.adapter, 'upsert', returning)
|
|
1502
|
-
|
|
1503
|
-
if (returning) {
|
|
1504
|
-
let statement: UpsertStatement<AnyTable> = {
|
|
1505
|
-
kind: 'upsert',
|
|
1506
|
-
table: this.#table,
|
|
1507
|
-
values: preparedValues,
|
|
1508
|
-
conflictTarget: options?.conflictTarget ? [...options.conflictTarget] : undefined,
|
|
1509
|
-
update: updateChanges,
|
|
1510
|
-
returning: normalizeReturningSelection(returning),
|
|
1511
|
-
}
|
|
1512
|
-
|
|
1513
|
-
let result = await this.#database[executeStatement](statement)
|
|
1514
|
-
let row = (normalizeRows(result.rows)[0] ?? null) as row | null
|
|
1515
|
-
|
|
1516
|
-
return {
|
|
1517
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1518
|
-
insertId: result.insertId,
|
|
1519
|
-
row,
|
|
1520
|
-
}
|
|
1521
|
-
}
|
|
1522
|
-
|
|
1523
|
-
let statement: UpsertStatement<AnyTable> = {
|
|
1524
|
-
kind: 'upsert',
|
|
1525
|
-
table: this.#table,
|
|
1526
|
-
values: preparedValues,
|
|
1527
|
-
conflictTarget: options?.conflictTarget ? [...options.conflictTarget] : undefined,
|
|
1528
|
-
update: updateChanges,
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
let result = await this.#database[executeStatement](statement)
|
|
1532
|
-
let metadata: WriteResult = {
|
|
1533
|
-
affectedRows: result.affectedRows ?? 0,
|
|
1534
|
-
insertId: result.insertId,
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
return metadata
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
#toSelectStatement(): SelectStatement<AnyTable> {
|
|
1541
|
-
return {
|
|
1542
|
-
kind: 'select',
|
|
1543
|
-
table: this.#table,
|
|
1544
|
-
select: cloneSelection(this.#state.select),
|
|
1545
|
-
distinct: this.#state.distinct,
|
|
1546
|
-
joins: [...this.#state.joins],
|
|
1547
|
-
where: [...this.#state.where],
|
|
1548
|
-
groupBy: [...this.#state.groupBy],
|
|
1549
|
-
having: [...this.#state.having],
|
|
1550
|
-
orderBy: [...this.#state.orderBy],
|
|
1551
|
-
limit: this.#state.limit,
|
|
1552
|
-
offset: this.#state.offset,
|
|
1553
|
-
}
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
#clone(
|
|
1557
|
-
patch: Partial<QueryState>,
|
|
1558
|
-
): QueryBuilder<columnTypes, row, loaded, tableName, primaryKey> {
|
|
1559
|
-
return new QueryBuilder(this.#database, this.#table, {
|
|
1560
|
-
select: patch.select ?? cloneSelection(this.#state.select),
|
|
1561
|
-
distinct: patch.distinct ?? this.#state.distinct,
|
|
1562
|
-
joins: patch.joins ? [...patch.joins] : [...this.#state.joins],
|
|
1563
|
-
where: patch.where ? [...patch.where] : [...this.#state.where],
|
|
1564
|
-
groupBy: patch.groupBy ? [...patch.groupBy] : [...this.#state.groupBy],
|
|
1565
|
-
having: patch.having ? [...patch.having] : [...this.#state.having],
|
|
1566
|
-
orderBy: patch.orderBy ? [...patch.orderBy] : [...this.#state.orderBy],
|
|
1567
|
-
limit: patch.limit === undefined ? this.#state.limit : patch.limit,
|
|
1568
|
-
offset: patch.offset === undefined ? this.#state.offset : patch.offset,
|
|
1569
|
-
with: patch.with ? { ...patch.with } : { ...this.#state.with },
|
|
1570
|
-
})
|
|
1571
|
-
}
|
|
1572
|
-
}
|
|
1573
|
-
|
|
1574
|
-
async function loadRelationsForRows(
|
|
1575
|
-
database: DatabaseRuntime,
|
|
1576
|
-
sourceTable: AnyTable,
|
|
1577
|
-
rows: Record<string, unknown>[],
|
|
1578
|
-
relationMap: Record<string, AnyRelation>,
|
|
1579
|
-
): Promise<Record<string, unknown>[]> {
|
|
1580
|
-
let output = rows.map((row) => ({ ...row }))
|
|
1581
|
-
|
|
1582
|
-
let relationNames = Object.keys(relationMap)
|
|
1583
|
-
|
|
1584
|
-
for (let relationName of relationNames) {
|
|
1585
|
-
let relation = relationMap[relationName]
|
|
1586
|
-
|
|
1587
|
-
if (relation.sourceTable !== sourceTable) {
|
|
1588
|
-
throw new DataTableQueryError(
|
|
1589
|
-
'Relation "' +
|
|
1590
|
-
relationName +
|
|
1591
|
-
'" is not defined for source table "' +
|
|
1592
|
-
getTableName(sourceTable) +
|
|
1593
|
-
'"',
|
|
1594
|
-
)
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
let values = await resolveRelationValues(database, output, relation)
|
|
1598
|
-
let index = 0
|
|
1599
|
-
|
|
1600
|
-
while (index < output.length) {
|
|
1601
|
-
output[index][relationName] = values[index]
|
|
1602
|
-
index += 1
|
|
1603
|
-
}
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
return output
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
async function resolveRelationValues(
|
|
1610
|
-
database: DatabaseRuntime,
|
|
1611
|
-
sourceRows: Record<string, unknown>[],
|
|
1612
|
-
relation: AnyRelation,
|
|
1613
|
-
): Promise<unknown[]> {
|
|
1614
|
-
if (relation.relationKind === 'hasManyThrough') {
|
|
1615
|
-
return loadHasManyThroughValues(database, sourceRows, relation)
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
|
-
return loadDirectRelationValues(database, sourceRows, relation)
|
|
1619
|
-
}
|
|
1620
|
-
|
|
1621
|
-
async function loadDirectRelationValues(
|
|
1622
|
-
database: DatabaseRuntime,
|
|
1623
|
-
sourceRows: Record<string, unknown>[],
|
|
1624
|
-
relation: AnyRelation,
|
|
1625
|
-
): Promise<unknown[]> {
|
|
1626
|
-
if (sourceRows.length === 0) {
|
|
1627
|
-
return []
|
|
1628
|
-
}
|
|
1629
|
-
|
|
1630
|
-
let sourceTuples = uniqueTuples(sourceRows, relation.sourceKey)
|
|
1631
|
-
|
|
1632
|
-
if (sourceTuples.length === 0) {
|
|
1633
|
-
return sourceRows.map(() => (relation.cardinality === 'many' ? [] : null))
|
|
1634
|
-
}
|
|
1635
|
-
|
|
1636
|
-
let query = database.query(relation.targetTable)
|
|
1637
|
-
let linkPredicate = buildLinkPredicate(relation.targetKey, sourceTuples)
|
|
1638
|
-
|
|
1639
|
-
if (linkPredicate) {
|
|
1640
|
-
query = query.where(linkPredicate as Predicate<QueryColumnName<typeof relation.targetTable>>)
|
|
1641
|
-
}
|
|
1642
|
-
|
|
1643
|
-
query = applyRelationModifiers(query, relation, {
|
|
1644
|
-
includePagination: false,
|
|
1645
|
-
})
|
|
1646
|
-
|
|
1647
|
-
let relatedRows = (await query.all()) as unknown as Record<string, unknown>[]
|
|
1648
|
-
let grouped = groupRowsByTuple(relatedRows, relation.targetKey)
|
|
1649
|
-
|
|
1650
|
-
return sourceRows.map((sourceRow) => {
|
|
1651
|
-
let key = getCompositeKey(sourceRow, relation.sourceKey)
|
|
1652
|
-
let matches = grouped.get(key) ?? []
|
|
1653
|
-
let pagedMatches = applyPagination(matches, relation.modifiers.limit, relation.modifiers.offset)
|
|
1654
|
-
|
|
1655
|
-
if (relation.cardinality === 'many') {
|
|
1656
|
-
return pagedMatches
|
|
1657
|
-
}
|
|
1658
|
-
|
|
1659
|
-
return pagedMatches[0] ?? null
|
|
1660
|
-
})
|
|
1661
|
-
}
|
|
1662
|
-
|
|
1663
|
-
async function loadHasManyThroughValues(
|
|
1664
|
-
database: DatabaseRuntime,
|
|
1665
|
-
sourceRows: Record<string, unknown>[],
|
|
1666
|
-
relation: AnyRelation,
|
|
1667
|
-
): Promise<unknown[]> {
|
|
1668
|
-
if (!relation.through) {
|
|
1669
|
-
throw new DataTableQueryError('hasManyThrough relation is missing through metadata')
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
if (sourceRows.length === 0) {
|
|
1673
|
-
return []
|
|
1674
|
-
}
|
|
1675
|
-
|
|
1676
|
-
let throughRelation = relation.through.relation
|
|
1677
|
-
let sourceTuples = uniqueTuples(sourceRows, throughRelation.sourceKey)
|
|
1678
|
-
|
|
1679
|
-
if (sourceTuples.length === 0) {
|
|
1680
|
-
return sourceRows.map(() => [])
|
|
1681
|
-
}
|
|
1682
|
-
|
|
1683
|
-
let throughQuery = database.query(throughRelation.targetTable)
|
|
1684
|
-
let throughPredicate = buildLinkPredicate(throughRelation.targetKey, sourceTuples)
|
|
1685
|
-
|
|
1686
|
-
if (throughPredicate) {
|
|
1687
|
-
throughQuery = throughQuery.where(
|
|
1688
|
-
throughPredicate as Predicate<QueryColumnName<typeof throughRelation.targetTable>>,
|
|
1689
|
-
)
|
|
1690
|
-
}
|
|
1691
|
-
|
|
1692
|
-
throughQuery = applyRelationModifiers(throughQuery, throughRelation, {
|
|
1693
|
-
includePagination: false,
|
|
1694
|
-
})
|
|
1695
|
-
|
|
1696
|
-
let throughRows = (await throughQuery.all()) as unknown as Record<string, unknown>[]
|
|
1697
|
-
|
|
1698
|
-
if (throughRows.length === 0) {
|
|
1699
|
-
return sourceRows.map(() => [])
|
|
1700
|
-
}
|
|
1701
|
-
|
|
1702
|
-
let throughRowsBySource = groupRowsByTuple(throughRows, throughRelation.targetKey)
|
|
1703
|
-
let pagedThroughRowsBySource = new Map<string, Record<string, unknown>[]>()
|
|
1704
|
-
let pagedThroughRows: Record<string, unknown>[] = []
|
|
1705
|
-
|
|
1706
|
-
for (let sourceRow of sourceRows) {
|
|
1707
|
-
let sourceKey = getCompositeKey(sourceRow, throughRelation.sourceKey)
|
|
1708
|
-
let matchedThroughRows = throughRowsBySource.get(sourceKey) ?? []
|
|
1709
|
-
let pagedMatchedRows = applyPagination(
|
|
1710
|
-
matchedThroughRows,
|
|
1711
|
-
throughRelation.modifiers.limit,
|
|
1712
|
-
throughRelation.modifiers.offset,
|
|
1713
|
-
)
|
|
1714
|
-
|
|
1715
|
-
pagedThroughRowsBySource.set(sourceKey, pagedMatchedRows)
|
|
1716
|
-
pagedThroughRows.push(...pagedMatchedRows)
|
|
1717
|
-
}
|
|
1718
|
-
|
|
1719
|
-
let throughTuples = uniqueTuples(pagedThroughRows, relation.through.throughSourceKey)
|
|
1720
|
-
|
|
1721
|
-
if (throughTuples.length === 0) {
|
|
1722
|
-
return sourceRows.map(() => [])
|
|
1723
|
-
}
|
|
1724
|
-
|
|
1725
|
-
let targetQuery = database.query(relation.targetTable)
|
|
1726
|
-
let targetPredicate = buildLinkPredicate(relation.through.throughTargetKey, throughTuples)
|
|
1727
|
-
|
|
1728
|
-
if (targetPredicate) {
|
|
1729
|
-
targetQuery = targetQuery.where(
|
|
1730
|
-
targetPredicate as Predicate<QueryColumnName<typeof relation.targetTable>>,
|
|
1731
|
-
)
|
|
1732
|
-
}
|
|
1733
|
-
|
|
1734
|
-
targetQuery = applyRelationModifiers(targetQuery, relation, {
|
|
1735
|
-
includePagination: false,
|
|
1736
|
-
})
|
|
1737
|
-
|
|
1738
|
-
let relatedRows = (await targetQuery.all()) as unknown as Record<string, unknown>[]
|
|
1739
|
-
let targetRowsByThrough = groupRowsByTuple(relatedRows, relation.through.throughTargetKey)
|
|
1740
|
-
|
|
1741
|
-
return sourceRows.map((sourceRow) => {
|
|
1742
|
-
let sourceKey = getCompositeKey(sourceRow, throughRelation.sourceKey)
|
|
1743
|
-
let matchedThroughRows = pagedThroughRowsBySource.get(sourceKey) ?? []
|
|
1744
|
-
let outputRows: Record<string, unknown>[] = []
|
|
1745
|
-
let seen = new Set<string>()
|
|
1746
|
-
|
|
1747
|
-
for (let throughRow of matchedThroughRows) {
|
|
1748
|
-
let throughKey = getCompositeKey(throughRow, relation.through!.throughSourceKey)
|
|
1749
|
-
let rowsForThrough = targetRowsByThrough.get(throughKey) ?? []
|
|
1750
|
-
|
|
1751
|
-
for (let row of rowsForThrough) {
|
|
1752
|
-
let rowIdentity = getCompositeKey(row, getTablePrimaryKey(relation.targetTable))
|
|
1753
|
-
|
|
1754
|
-
if (!seen.has(rowIdentity)) {
|
|
1755
|
-
seen.add(rowIdentity)
|
|
1756
|
-
outputRows.push(row)
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
|
|
1761
|
-
return applyPagination(outputRows, relation.modifiers.limit, relation.modifiers.offset)
|
|
1762
|
-
})
|
|
1763
|
-
}
|
|
1764
|
-
|
|
1765
|
-
function applyRelationModifiers<table extends AnyTable>(
|
|
1766
|
-
query: QueryForTable<table>,
|
|
1767
|
-
relation: Relation<any, table, any, any>,
|
|
1768
|
-
options: { includePagination: boolean },
|
|
1769
|
-
): QueryForTable<table, any> {
|
|
1770
|
-
let next = query
|
|
1771
|
-
|
|
1772
|
-
for (let predicate of relation.modifiers.where) {
|
|
1773
|
-
next = next.where(predicate)
|
|
1774
|
-
}
|
|
1775
|
-
|
|
1776
|
-
for (let clause of relation.modifiers.orderBy) {
|
|
1777
|
-
next = next.orderBy(clause.column as QueryColumns<QueryColumnTypeMap<table>>, clause.direction)
|
|
1778
|
-
}
|
|
1779
|
-
|
|
1780
|
-
if (options.includePagination && relation.modifiers.limit !== undefined) {
|
|
1781
|
-
next = next.limit(relation.modifiers.limit)
|
|
1782
|
-
}
|
|
1783
|
-
|
|
1784
|
-
if (options.includePagination && relation.modifiers.offset !== undefined) {
|
|
1785
|
-
next = next.offset(relation.modifiers.offset)
|
|
1786
|
-
}
|
|
1787
|
-
|
|
1788
|
-
if (Object.keys(relation.modifiers.with).length > 0) {
|
|
1789
|
-
next = next.with(relation.modifiers.with)
|
|
1790
|
-
}
|
|
1791
|
-
|
|
1792
|
-
return next
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
function applyPagination<row>(
|
|
1796
|
-
rows: row[],
|
|
1797
|
-
limit: number | undefined,
|
|
1798
|
-
offset: number | undefined,
|
|
1799
|
-
): row[] {
|
|
1800
|
-
let offsetRows = offset === undefined ? rows : rows.slice(offset)
|
|
1801
|
-
return limit === undefined ? offsetRows : offsetRows.slice(0, limit)
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
function normalizeRows(rows: AdapterResult['rows']): Record<string, unknown>[] {
|
|
1805
|
-
if (!rows) {
|
|
1806
|
-
return []
|
|
1807
|
-
}
|
|
1808
|
-
|
|
1809
|
-
return rows.map((row) => ({ ...row }))
|
|
1810
|
-
}
|
|
1811
|
-
|
|
1812
|
-
function hasScopedWriteModifiers(state: QueryState): boolean {
|
|
1813
|
-
return state.orderBy.length > 0 || state.limit !== undefined || state.offset !== undefined
|
|
1814
|
-
}
|
|
1815
|
-
|
|
1816
|
-
function asQueryTableInput<table extends AnyTable>(
|
|
1817
|
-
table: table,
|
|
1818
|
-
): QueryTableInput<TableName<table>, TableRow<table>, TablePrimaryKey<table>> {
|
|
1819
|
-
return table as unknown as QueryTableInput<
|
|
1820
|
-
TableName<table>,
|
|
1821
|
-
TableRow<table>,
|
|
1822
|
-
TablePrimaryKey<table>
|
|
1823
|
-
>
|
|
1824
|
-
}
|
|
1825
|
-
|
|
1826
|
-
function getPrimaryKeyWhere<table extends AnyTable>(
|
|
1827
|
-
table: table,
|
|
1828
|
-
value: PrimaryKeyInput<table>,
|
|
1829
|
-
): SingleTableWhere<table> {
|
|
1830
|
-
return getPrimaryKeyObject(table, value as any) as SingleTableWhere<table>
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
function getPrimaryKeyWhereFromRow<table extends AnyTable>(
|
|
1834
|
-
table: table,
|
|
1835
|
-
row: Record<string, unknown>,
|
|
1836
|
-
): SingleTableWhere<table> {
|
|
1837
|
-
let where: Record<string, unknown> = {}
|
|
1838
|
-
|
|
1839
|
-
for (let key of getTablePrimaryKey(table) as string[]) {
|
|
1840
|
-
where[key] = row[key]
|
|
1841
|
-
}
|
|
1842
|
-
|
|
1843
|
-
return where as SingleTableWhere<table>
|
|
1844
|
-
}
|
|
1845
|
-
|
|
1846
|
-
function resolveCreateRowWhere<table extends AnyTable>(
|
|
1847
|
-
table: table,
|
|
1848
|
-
values: Partial<TableRow<table>>,
|
|
1849
|
-
insertId: unknown,
|
|
1850
|
-
): SingleTableWhere<table> {
|
|
1851
|
-
let primaryKey = getTablePrimaryKey(table) as string[]
|
|
1852
|
-
|
|
1853
|
-
if (primaryKey.length === 1) {
|
|
1854
|
-
let key = primaryKey[0]
|
|
1855
|
-
|
|
1856
|
-
if (Object.prototype.hasOwnProperty.call(values, key)) {
|
|
1857
|
-
return {
|
|
1858
|
-
[key]: (values as Record<string, unknown>)[key],
|
|
1859
|
-
} as SingleTableWhere<table>
|
|
1860
|
-
}
|
|
1861
|
-
|
|
1862
|
-
if (insertId !== undefined) {
|
|
1863
|
-
return {
|
|
1864
|
-
[key]: insertId,
|
|
1865
|
-
} as SingleTableWhere<table>
|
|
1866
|
-
}
|
|
1867
|
-
}
|
|
1868
|
-
|
|
1869
|
-
let where: Record<string, unknown> = {}
|
|
1870
|
-
|
|
1871
|
-
for (let key of primaryKey) {
|
|
1872
|
-
if (!Object.prototype.hasOwnProperty.call(values, key)) {
|
|
1873
|
-
throw new DataTableQueryError(
|
|
1874
|
-
'create({ returnRow: true }) requires primary key values for table "' +
|
|
1875
|
-
getTableName(table) +
|
|
1876
|
-
'" when adapter does not support RETURNING',
|
|
1877
|
-
)
|
|
1878
|
-
}
|
|
1879
|
-
|
|
1880
|
-
where[key] = (values as Record<string, unknown>)[key]
|
|
1881
|
-
}
|
|
1882
|
-
|
|
1883
|
-
return where as SingleTableWhere<table>
|
|
1884
|
-
}
|
|
1885
|
-
|
|
1886
|
-
function normalizeOrderByInput<table extends AnyTable>(
|
|
1887
|
-
input: OrderByInput<table> | undefined,
|
|
1888
|
-
): OrderByTuple<table>[] {
|
|
1889
|
-
if (!input) {
|
|
1890
|
-
return []
|
|
1891
|
-
}
|
|
1892
|
-
|
|
1893
|
-
if (input.length === 0) {
|
|
1894
|
-
return []
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
if (Array.isArray(input[0])) {
|
|
1898
|
-
return input as OrderByTuple<table>[]
|
|
1899
|
-
}
|
|
1900
|
-
|
|
1901
|
-
return [input as OrderByTuple<table>]
|
|
1902
|
-
}
|
|
1903
|
-
|
|
1904
|
-
function toWriteResult(result: WriteResult | WriteRowsResult<unknown>): WriteResult {
|
|
1905
|
-
return {
|
|
1906
|
-
affectedRows: result.affectedRows,
|
|
1907
|
-
insertId: result.insertId,
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
|
|
1911
|
-
type WriteStatePolicy = {
|
|
1912
|
-
where: boolean
|
|
1913
|
-
orderBy: boolean
|
|
1914
|
-
limit: boolean
|
|
1915
|
-
offset: boolean
|
|
1916
|
-
}
|
|
1917
|
-
|
|
1918
|
-
function assertWriteState(
|
|
1919
|
-
state: QueryState,
|
|
1920
|
-
operation: 'insert' | 'insertMany' | 'update' | 'delete' | 'upsert',
|
|
1921
|
-
policy: WriteStatePolicy,
|
|
1922
|
-
): void {
|
|
1923
|
-
let unsupported: string[] = []
|
|
1924
|
-
|
|
1925
|
-
if (state.select !== '*') {
|
|
1926
|
-
unsupported.push('select()')
|
|
1927
|
-
}
|
|
1928
|
-
|
|
1929
|
-
if (state.distinct) {
|
|
1930
|
-
unsupported.push('distinct()')
|
|
1931
|
-
}
|
|
1932
|
-
|
|
1933
|
-
if (state.joins.length > 0) {
|
|
1934
|
-
unsupported.push('join()')
|
|
1935
|
-
}
|
|
1936
|
-
|
|
1937
|
-
if (state.groupBy.length > 0) {
|
|
1938
|
-
unsupported.push('groupBy()')
|
|
1939
|
-
}
|
|
1940
|
-
|
|
1941
|
-
if (state.having.length > 0) {
|
|
1942
|
-
unsupported.push('having()')
|
|
1943
|
-
}
|
|
1944
|
-
|
|
1945
|
-
if (Object.keys(state.with).length > 0) {
|
|
1946
|
-
unsupported.push('with()')
|
|
1947
|
-
}
|
|
1948
|
-
|
|
1949
|
-
if (!policy.where && state.where.length > 0) {
|
|
1950
|
-
unsupported.push('where()')
|
|
1951
|
-
}
|
|
1952
|
-
|
|
1953
|
-
if (!policy.orderBy && state.orderBy.length > 0) {
|
|
1954
|
-
unsupported.push('orderBy()')
|
|
1955
|
-
}
|
|
1956
|
-
|
|
1957
|
-
if (!policy.limit && state.limit !== undefined) {
|
|
1958
|
-
unsupported.push('limit()')
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
if (!policy.offset && state.offset !== undefined) {
|
|
1962
|
-
unsupported.push('offset()')
|
|
1963
|
-
}
|
|
1964
|
-
|
|
1965
|
-
if (unsupported.length > 0) {
|
|
1966
|
-
throw new DataTableQueryError(
|
|
1967
|
-
operation + '() does not support these query modifiers: ' + unsupported.join(', '),
|
|
1968
|
-
)
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
async function loadPrimaryKeyRowsForScope<table extends AnyTable>(
|
|
1973
|
-
database: Database,
|
|
1974
|
-
table: table,
|
|
1975
|
-
state: QueryState,
|
|
1976
|
-
): Promise<Record<string, unknown>[]> {
|
|
1977
|
-
let query: QueryForTable<table> = database.query<
|
|
1978
|
-
TableName<table>,
|
|
1979
|
-
TableRow<table>,
|
|
1980
|
-
TablePrimaryKey<table>
|
|
1981
|
-
>(table as unknown as QueryTableInput<TableName<table>, TableRow<table>, TablePrimaryKey<table>>)
|
|
1982
|
-
|
|
1983
|
-
for (let predicate of state.where) {
|
|
1984
|
-
query = query.where(predicate as Predicate<QueryColumnName<table>>)
|
|
1985
|
-
}
|
|
1986
|
-
|
|
1987
|
-
for (let clause of state.orderBy) {
|
|
1988
|
-
query = query.orderBy(
|
|
1989
|
-
clause.column as QueryColumns<QueryColumnTypeMap<table>>,
|
|
1990
|
-
clause.direction,
|
|
1991
|
-
)
|
|
1992
|
-
}
|
|
1993
|
-
|
|
1994
|
-
if (state.limit !== undefined) {
|
|
1995
|
-
query = query.limit(state.limit)
|
|
1996
|
-
}
|
|
1997
|
-
|
|
1998
|
-
if (state.offset !== undefined) {
|
|
1999
|
-
query = query.offset(state.offset)
|
|
2000
|
-
}
|
|
2001
|
-
|
|
2002
|
-
let rows = await query
|
|
2003
|
-
.select(...(getTablePrimaryKey(table) as (keyof TableRow<table> & string)[]))
|
|
2004
|
-
.all()
|
|
2005
|
-
let primaryKeys = getTablePrimaryKey(table) as string[]
|
|
2006
|
-
|
|
2007
|
-
return rows.map((row) => {
|
|
2008
|
-
let keyObject: Record<string, unknown> = {}
|
|
2009
|
-
|
|
2010
|
-
for (let key of rowKeys(row as Record<string, unknown>, primaryKeys)) {
|
|
2011
|
-
keyObject[key] = (row as Record<string, unknown>)[key]
|
|
2012
|
-
}
|
|
2013
|
-
|
|
2014
|
-
return keyObject
|
|
2015
|
-
})
|
|
2016
|
-
}
|
|
2017
|
-
|
|
2018
|
-
function createInitialQueryState(): QueryState {
|
|
2019
|
-
return {
|
|
2020
|
-
select: '*',
|
|
2021
|
-
distinct: false,
|
|
2022
|
-
joins: [],
|
|
2023
|
-
where: [],
|
|
2024
|
-
groupBy: [],
|
|
2025
|
-
having: [],
|
|
2026
|
-
orderBy: [],
|
|
2027
|
-
with: {},
|
|
2028
|
-
}
|
|
2029
|
-
}
|
|
2030
|
-
|
|
2031
|
-
function cloneSelection(selection: '*' | SelectColumn[]): '*' | SelectColumn[] {
|
|
2032
|
-
if (selection === '*') {
|
|
2033
|
-
return '*'
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
return selection.map((column) => ({ ...column }))
|
|
2037
|
-
}
|
|
843
|
+
export function createDatabaseWithTransaction(
|
|
844
|
+
adapter: DatabaseAdapter,
|
|
845
|
+
token: TransactionToken,
|
|
846
|
+
options?: { now?: () => unknown },
|
|
847
|
+
): Database {
|
|
848
|
+
return Database[createInternalDatabase](adapter, options, {
|
|
849
|
+
token,
|
|
850
|
+
savepointCounter: { value: 0 },
|
|
851
|
+
})
|
|
852
|
+
}
|
|
2038
853
|
|
|
2039
854
|
function defaultNow(): Date {
|
|
2040
855
|
return new Date()
|
|
2041
856
|
}
|
|
2042
|
-
|
|
2043
|
-
function prepareInsertValues<table extends AnyTable>(
|
|
2044
|
-
table: table,
|
|
2045
|
-
values: Partial<TableRow<table>>,
|
|
2046
|
-
now: unknown,
|
|
2047
|
-
touch: boolean,
|
|
2048
|
-
): Record<string, unknown> {
|
|
2049
|
-
let output = validateWriteValues(table, values)
|
|
2050
|
-
let timestamps = getTableTimestamps(table)
|
|
2051
|
-
let columns = getTableColumns(table)
|
|
2052
|
-
|
|
2053
|
-
if (touch && timestamps) {
|
|
2054
|
-
let createdAt = timestamps.createdAt
|
|
2055
|
-
let updatedAt = timestamps.updatedAt
|
|
2056
|
-
|
|
2057
|
-
if (
|
|
2058
|
-
Object.prototype.hasOwnProperty.call(columns, createdAt) &&
|
|
2059
|
-
output[createdAt] === undefined
|
|
2060
|
-
) {
|
|
2061
|
-
output[createdAt] = now
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
if (
|
|
2065
|
-
Object.prototype.hasOwnProperty.call(columns, updatedAt) &&
|
|
2066
|
-
output[updatedAt] === undefined
|
|
2067
|
-
) {
|
|
2068
|
-
output[updatedAt] = now
|
|
2069
|
-
}
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
return output
|
|
2073
|
-
}
|
|
2074
|
-
|
|
2075
|
-
function prepareUpdateValues<table extends AnyTable>(
|
|
2076
|
-
table: table,
|
|
2077
|
-
values: Partial<TableRow<table>>,
|
|
2078
|
-
now: unknown,
|
|
2079
|
-
touch: boolean,
|
|
2080
|
-
): Record<string, unknown> {
|
|
2081
|
-
let output = validateWriteValues(table, values)
|
|
2082
|
-
let timestamps = getTableTimestamps(table)
|
|
2083
|
-
let columns = getTableColumns(table)
|
|
2084
|
-
|
|
2085
|
-
if (touch && timestamps) {
|
|
2086
|
-
let updatedAt = timestamps.updatedAt
|
|
2087
|
-
|
|
2088
|
-
if (
|
|
2089
|
-
Object.prototype.hasOwnProperty.call(columns, updatedAt) &&
|
|
2090
|
-
output[updatedAt] === undefined
|
|
2091
|
-
) {
|
|
2092
|
-
output[updatedAt] = now
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
return output
|
|
2097
|
-
}
|
|
2098
|
-
|
|
2099
|
-
function validateWriteValues<table extends AnyTable>(
|
|
2100
|
-
table: table,
|
|
2101
|
-
values: Partial<TableRow<table>>,
|
|
2102
|
-
): Record<string, unknown> {
|
|
2103
|
-
let columns = getTableColumns(table)
|
|
2104
|
-
let tableName = getTableName(table)
|
|
2105
|
-
let result = validatePartialRow(table, values)
|
|
2106
|
-
|
|
2107
|
-
if ('issues' in result) {
|
|
2108
|
-
let firstIssue = result.issues[0]
|
|
2109
|
-
let issuePath = firstIssue?.path
|
|
2110
|
-
let firstPathSegment = issuePath && issuePath.length > 0 ? issuePath[0] : undefined
|
|
2111
|
-
let column = typeof firstPathSegment === 'string' ? firstPathSegment : undefined
|
|
2112
|
-
|
|
2113
|
-
if (column && !Object.prototype.hasOwnProperty.call(columns, column)) {
|
|
2114
|
-
throw new DataTableValidationError(
|
|
2115
|
-
'Unknown column "' + column + '" for table "' + tableName + '"',
|
|
2116
|
-
[],
|
|
2117
|
-
)
|
|
2118
|
-
}
|
|
2119
|
-
|
|
2120
|
-
if (column) {
|
|
2121
|
-
throw new DataTableValidationError(
|
|
2122
|
-
'Invalid value for column "' + column + '" in table "' + tableName + '"',
|
|
2123
|
-
result.issues,
|
|
2124
|
-
{
|
|
2125
|
-
metadata: {
|
|
2126
|
-
table: tableName,
|
|
2127
|
-
column,
|
|
2128
|
-
},
|
|
2129
|
-
},
|
|
2130
|
-
)
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
throw new DataTableValidationError(
|
|
2134
|
-
'Invalid value for table "' + tableName + '"',
|
|
2135
|
-
result.issues,
|
|
2136
|
-
{
|
|
2137
|
-
metadata: {
|
|
2138
|
-
table: tableName,
|
|
2139
|
-
},
|
|
2140
|
-
},
|
|
2141
|
-
)
|
|
2142
|
-
}
|
|
2143
|
-
|
|
2144
|
-
return result.value as Record<string, unknown>
|
|
2145
|
-
}
|
|
2146
|
-
|
|
2147
|
-
type ResolvedPredicateColumn = {
|
|
2148
|
-
tableName: string
|
|
2149
|
-
columnName: string
|
|
2150
|
-
schema: unknown
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
function createPredicateColumnResolver(
|
|
2154
|
-
tables: AnyTable[],
|
|
2155
|
-
): (column: string) => ResolvedPredicateColumn {
|
|
2156
|
-
let qualifiedColumns = new Map<string, ResolvedPredicateColumn>()
|
|
2157
|
-
let unqualifiedColumns = new Map<string, ResolvedPredicateColumn>()
|
|
2158
|
-
let ambiguousColumns = new Set<string>()
|
|
2159
|
-
|
|
2160
|
-
for (let table of tables) {
|
|
2161
|
-
let tableColumns = getTableColumns(table)
|
|
2162
|
-
let tableName = getTableName(table)
|
|
2163
|
-
|
|
2164
|
-
for (let columnName in tableColumns) {
|
|
2165
|
-
if (!Object.prototype.hasOwnProperty.call(tableColumns, columnName)) {
|
|
2166
|
-
continue
|
|
2167
|
-
}
|
|
2168
|
-
|
|
2169
|
-
let resolvedColumn: ResolvedPredicateColumn = {
|
|
2170
|
-
tableName,
|
|
2171
|
-
columnName,
|
|
2172
|
-
schema: tableColumns[columnName],
|
|
2173
|
-
}
|
|
2174
|
-
|
|
2175
|
-
qualifiedColumns.set(tableName + '.' + columnName, resolvedColumn)
|
|
2176
|
-
|
|
2177
|
-
if (ambiguousColumns.has(columnName)) {
|
|
2178
|
-
continue
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
if (unqualifiedColumns.has(columnName)) {
|
|
2182
|
-
unqualifiedColumns.delete(columnName)
|
|
2183
|
-
ambiguousColumns.add(columnName)
|
|
2184
|
-
continue
|
|
2185
|
-
}
|
|
2186
|
-
|
|
2187
|
-
unqualifiedColumns.set(columnName, resolvedColumn)
|
|
2188
|
-
}
|
|
2189
|
-
}
|
|
2190
|
-
|
|
2191
|
-
return function resolveColumn(column: string): ResolvedPredicateColumn {
|
|
2192
|
-
let qualified = qualifiedColumns.get(column)
|
|
2193
|
-
|
|
2194
|
-
if (qualified) {
|
|
2195
|
-
return qualified
|
|
2196
|
-
}
|
|
2197
|
-
|
|
2198
|
-
if (column.includes('.')) {
|
|
2199
|
-
throw new DataTableQueryError('Unknown predicate column "' + column + '"')
|
|
2200
|
-
}
|
|
2201
|
-
|
|
2202
|
-
if (ambiguousColumns.has(column)) {
|
|
2203
|
-
throw new DataTableQueryError(
|
|
2204
|
-
'Ambiguous predicate column "' + column + '". Use a qualified column name',
|
|
2205
|
-
)
|
|
2206
|
-
}
|
|
2207
|
-
|
|
2208
|
-
let unqualified = unqualifiedColumns.get(column)
|
|
2209
|
-
|
|
2210
|
-
if (!unqualified) {
|
|
2211
|
-
throw new DataTableQueryError('Unknown predicate column "' + column + '"')
|
|
2212
|
-
}
|
|
2213
|
-
|
|
2214
|
-
return unqualified
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
|
|
2218
|
-
function normalizePredicateValues(
|
|
2219
|
-
predicate: Predicate,
|
|
2220
|
-
resolveColumn: (column: string) => ResolvedPredicateColumn,
|
|
2221
|
-
): Predicate {
|
|
2222
|
-
if (predicate.type === 'comparison') {
|
|
2223
|
-
let column = resolveColumn(predicate.column)
|
|
2224
|
-
|
|
2225
|
-
if (predicate.valueType === 'column') {
|
|
2226
|
-
resolveColumn(predicate.value)
|
|
2227
|
-
return predicate
|
|
2228
|
-
}
|
|
2229
|
-
|
|
2230
|
-
if (
|
|
2231
|
-
(predicate.operator === 'eq' || predicate.operator === 'ne') &&
|
|
2232
|
-
(predicate.value === null || predicate.value === undefined)
|
|
2233
|
-
) {
|
|
2234
|
-
return predicate
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
if (predicate.operator === 'in' || predicate.operator === 'notIn') {
|
|
2238
|
-
if (!Array.isArray(predicate.value)) {
|
|
2239
|
-
throw new DataTableValidationError(
|
|
2240
|
-
'Invalid filter value for column "' +
|
|
2241
|
-
column.columnName +
|
|
2242
|
-
'" in table "' +
|
|
2243
|
-
column.tableName +
|
|
2244
|
-
'"',
|
|
2245
|
-
[{ message: 'Expected an array value for "' + predicate.operator + '" predicate' }],
|
|
2246
|
-
{
|
|
2247
|
-
metadata: {
|
|
2248
|
-
table: column.tableName,
|
|
2249
|
-
column: column.columnName,
|
|
2250
|
-
},
|
|
2251
|
-
},
|
|
2252
|
-
)
|
|
2253
|
-
}
|
|
2254
|
-
|
|
2255
|
-
let parsedValues = predicate.value.map((value) => parsePredicateValue(column, value))
|
|
2256
|
-
|
|
2257
|
-
return {
|
|
2258
|
-
...predicate,
|
|
2259
|
-
value: parsedValues,
|
|
2260
|
-
}
|
|
2261
|
-
}
|
|
2262
|
-
|
|
2263
|
-
return {
|
|
2264
|
-
...predicate,
|
|
2265
|
-
value: parsePredicateValue(column, predicate.value),
|
|
2266
|
-
}
|
|
2267
|
-
}
|
|
2268
|
-
|
|
2269
|
-
if (predicate.type === 'between') {
|
|
2270
|
-
let column = resolveColumn(predicate.column)
|
|
2271
|
-
|
|
2272
|
-
return {
|
|
2273
|
-
...predicate,
|
|
2274
|
-
lower: parsePredicateValue(column, predicate.lower),
|
|
2275
|
-
upper: parsePredicateValue(column, predicate.upper),
|
|
2276
|
-
}
|
|
2277
|
-
}
|
|
2278
|
-
|
|
2279
|
-
if (predicate.type === 'null') {
|
|
2280
|
-
resolveColumn(predicate.column)
|
|
2281
|
-
return predicate
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
return {
|
|
2285
|
-
...predicate,
|
|
2286
|
-
predicates: predicate.predicates.map((child) => normalizePredicateValues(child, resolveColumn)),
|
|
2287
|
-
}
|
|
2288
|
-
}
|
|
2289
|
-
|
|
2290
|
-
function parsePredicateValue(column: ResolvedPredicateColumn, value: unknown): unknown {
|
|
2291
|
-
let result = parseSafe(column.schema as any, value) as
|
|
2292
|
-
| { success: true; value: unknown }
|
|
2293
|
-
| { success: false; issues: ReadonlyArray<unknown> }
|
|
2294
|
-
|
|
2295
|
-
if (!result.success) {
|
|
2296
|
-
throw new DataTableValidationError(
|
|
2297
|
-
'Invalid filter value for column "' +
|
|
2298
|
-
column.columnName +
|
|
2299
|
-
'" in table "' +
|
|
2300
|
-
column.tableName +
|
|
2301
|
-
'"',
|
|
2302
|
-
result.issues,
|
|
2303
|
-
{
|
|
2304
|
-
metadata: {
|
|
2305
|
-
table: column.tableName,
|
|
2306
|
-
column: column.columnName,
|
|
2307
|
-
},
|
|
2308
|
-
},
|
|
2309
|
-
)
|
|
2310
|
-
}
|
|
2311
|
-
|
|
2312
|
-
return result.value
|
|
2313
|
-
}
|
|
2314
|
-
|
|
2315
|
-
function uniqueTuples(rows: Record<string, unknown>[], columns: string[]): unknown[][] {
|
|
2316
|
-
let output: unknown[][] = []
|
|
2317
|
-
let seen = new Set<string>()
|
|
2318
|
-
|
|
2319
|
-
for (let row of rows) {
|
|
2320
|
-
let tuple = columns.map((column) => row[column])
|
|
2321
|
-
let key = tuple.map(stringifyForKey).join('::')
|
|
2322
|
-
|
|
2323
|
-
if (!seen.has(key)) {
|
|
2324
|
-
seen.add(key)
|
|
2325
|
-
output.push(tuple)
|
|
2326
|
-
}
|
|
2327
|
-
}
|
|
2328
|
-
|
|
2329
|
-
return output
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
|
-
function buildLinkPredicate(targetColumns: string[], tuples: unknown[][]): Predicate | undefined {
|
|
2333
|
-
if (tuples.length === 0) {
|
|
2334
|
-
return undefined
|
|
2335
|
-
}
|
|
2336
|
-
|
|
2337
|
-
if (targetColumns.length === 1) {
|
|
2338
|
-
return inList(
|
|
2339
|
-
targetColumns[0],
|
|
2340
|
-
tuples.map((tuple) => tuple[0]),
|
|
2341
|
-
)
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
let tuplePredicates = tuples.map((tuple) => {
|
|
2345
|
-
let comparisons = targetColumns.map((column, index) => eq(column, tuple[index]))
|
|
2346
|
-
|
|
2347
|
-
return and(...comparisons)
|
|
2348
|
-
})
|
|
2349
|
-
|
|
2350
|
-
return or(...tuplePredicates)
|
|
2351
|
-
}
|
|
2352
|
-
|
|
2353
|
-
function groupRowsByTuple(
|
|
2354
|
-
rows: Record<string, unknown>[],
|
|
2355
|
-
columns: string[],
|
|
2356
|
-
): Map<string, Record<string, unknown>[]> {
|
|
2357
|
-
let output = new Map<string, Record<string, unknown>[]>()
|
|
2358
|
-
|
|
2359
|
-
for (let row of rows) {
|
|
2360
|
-
let key = getCompositeKey(row, columns)
|
|
2361
|
-
let group = output.get(key)
|
|
2362
|
-
|
|
2363
|
-
if (group) {
|
|
2364
|
-
group.push(row)
|
|
2365
|
-
continue
|
|
2366
|
-
}
|
|
2367
|
-
|
|
2368
|
-
output.set(key, [row])
|
|
2369
|
-
}
|
|
2370
|
-
|
|
2371
|
-
return output
|
|
2372
|
-
}
|
|
2373
|
-
|
|
2374
|
-
function stringifyForKey(value: unknown): string {
|
|
2375
|
-
if (value === null) {
|
|
2376
|
-
return 'null'
|
|
2377
|
-
}
|
|
2378
|
-
|
|
2379
|
-
if (value === undefined) {
|
|
2380
|
-
return 'undefined'
|
|
2381
|
-
}
|
|
2382
|
-
|
|
2383
|
-
if (value instanceof Date) {
|
|
2384
|
-
return 'date:' + value.toISOString()
|
|
2385
|
-
}
|
|
2386
|
-
|
|
2387
|
-
if (typeof value === 'string') {
|
|
2388
|
-
return JSON.stringify(value)
|
|
2389
|
-
}
|
|
2390
|
-
|
|
2391
|
-
if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {
|
|
2392
|
-
return String(value)
|
|
2393
|
-
}
|
|
2394
|
-
|
|
2395
|
-
return JSON.stringify(value)
|
|
2396
|
-
}
|
|
2397
|
-
|
|
2398
|
-
function normalizeReturningSelection<row extends Record<string, unknown>>(
|
|
2399
|
-
returning: ReturningInput<row>,
|
|
2400
|
-
): ReturningSelection {
|
|
2401
|
-
if (returning === '*') {
|
|
2402
|
-
return '*'
|
|
2403
|
-
}
|
|
2404
|
-
|
|
2405
|
-
return [...returning]
|
|
2406
|
-
}
|
|
2407
|
-
|
|
2408
|
-
function buildPrimaryKeyPredicate<table extends AnyTable>(
|
|
2409
|
-
table: table,
|
|
2410
|
-
keyObjects: Record<string, unknown>[],
|
|
2411
|
-
): Predicate<TableColumnName<table>> | undefined {
|
|
2412
|
-
let primaryKey = getTablePrimaryKey(table)
|
|
2413
|
-
|
|
2414
|
-
if (keyObjects.length === 0) {
|
|
2415
|
-
return undefined
|
|
2416
|
-
}
|
|
2417
|
-
|
|
2418
|
-
if (primaryKey.length === 1) {
|
|
2419
|
-
let key = primaryKey[0] as TableColumnName<table>
|
|
2420
|
-
return inList(
|
|
2421
|
-
key,
|
|
2422
|
-
keyObjects.map((objectValue) => objectValue[key]),
|
|
2423
|
-
)
|
|
2424
|
-
}
|
|
2425
|
-
|
|
2426
|
-
let predicates = keyObjects.map((objectValue) => {
|
|
2427
|
-
let comparisons = primaryKey.map((key) => {
|
|
2428
|
-
let typedKey = key as TableColumnName<table>
|
|
2429
|
-
return eq(typedKey, objectValue[typedKey])
|
|
2430
|
-
})
|
|
2431
|
-
|
|
2432
|
-
return and(...comparisons)
|
|
2433
|
-
})
|
|
2434
|
-
|
|
2435
|
-
return or(...predicates)
|
|
2436
|
-
}
|
|
2437
|
-
|
|
2438
|
-
function rowKeys(row: Record<string, unknown>, keys: string[]): string[] {
|
|
2439
|
-
let output: string[] = []
|
|
2440
|
-
|
|
2441
|
-
for (let key of keys) {
|
|
2442
|
-
if (Object.prototype.hasOwnProperty.call(row, key)) {
|
|
2443
|
-
output.push(key)
|
|
2444
|
-
}
|
|
2445
|
-
}
|
|
2446
|
-
|
|
2447
|
-
return output
|
|
2448
|
-
}
|
|
2449
|
-
|
|
2450
|
-
function assertReturningCapability<row extends Record<string, unknown>>(
|
|
2451
|
-
adapter: DatabaseAdapter,
|
|
2452
|
-
operation: 'insert' | 'insertMany' | 'update' | 'delete' | 'upsert',
|
|
2453
|
-
returning: ReturningInput<row> | undefined,
|
|
2454
|
-
): void {
|
|
2455
|
-
if (returning && !adapter.capabilities.returning) {
|
|
2456
|
-
throw new DataTableQueryError(operation + '() returning is not supported by this adapter')
|
|
2457
|
-
}
|
|
2458
|
-
}
|