graphile-build-pg 4.12.0 → 4.12.3
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 +10 -3
- package/node8plus/GraphQLJSON.js.map +1 -1
- package/node8plus/QueryBuilder.js +3 -4
- package/node8plus/QueryBuilder.js.map +1 -1
- package/node8plus/index.d.ts +2 -6
- package/node8plus/index.js +84 -83
- package/node8plus/index.js.map +1 -1
- package/node8plus/inflections.js +1 -1
- package/node8plus/inflections.js.flow +1 -3
- package/node8plus/inflections.js.map +1 -1
- package/node8plus/omit.js +2 -2
- package/node8plus/omit.js.flow +1 -1
- package/node8plus/omit.js.map +1 -1
- package/node8plus/plugins/PgAllRows.js.flow +2 -3
- package/node8plus/plugins/PgAllRows.js.map +1 -1
- package/node8plus/plugins/PgBackwardRelationPlugin.js +2 -2
- package/node8plus/plugins/PgBackwardRelationPlugin.js.flow +22 -18
- package/node8plus/plugins/PgBackwardRelationPlugin.js.map +1 -1
- package/node8plus/plugins/PgBasicsPlugin.js +3 -3
- package/node8plus/plugins/PgBasicsPlugin.js.flow +120 -120
- package/node8plus/plugins/PgBasicsPlugin.js.map +1 -1
- package/node8plus/plugins/PgColumnsPlugin.js +1 -1
- package/node8plus/plugins/PgColumnsPlugin.js.flow +5 -2
- package/node8plus/plugins/PgColumnsPlugin.js.map +1 -1
- package/node8plus/plugins/PgComputedColumnsPlugin.js +4 -4
- package/node8plus/plugins/PgComputedColumnsPlugin.js.flow +1 -1
- package/node8plus/plugins/PgComputedColumnsPlugin.js.map +1 -1
- package/node8plus/plugins/PgConnectionArgCondition.js.flow +4 -4
- package/node8plus/plugins/PgConnectionArgCondition.js.map +1 -1
- package/node8plus/plugins/PgConnectionArgOrderBy.js +1 -1
- package/node8plus/plugins/PgConnectionArgOrderBy.js.flow +1 -1
- package/node8plus/plugins/PgConnectionArgOrderBy.js.map +1 -1
- package/node8plus/plugins/PgConnectionArgOrderByDefaultValue.js +2 -1
- package/node8plus/plugins/PgConnectionArgOrderByDefaultValue.js.flow +2 -1
- package/node8plus/plugins/PgConnectionArgOrderByDefaultValue.js.map +1 -1
- package/node8plus/plugins/PgForwardRelationPlugin.js.flow +9 -8
- package/node8plus/plugins/PgForwardRelationPlugin.js.map +1 -1
- package/node8plus/plugins/PgIntrospectionPlugin.js +5 -5
- package/node8plus/plugins/PgIntrospectionPlugin.js.flow +13 -12
- package/node8plus/plugins/PgIntrospectionPlugin.js.map +1 -1
- package/node8plus/plugins/PgJWTPlugin.js.flow +2 -3
- package/node8plus/plugins/PgJWTPlugin.js.map +1 -1
- package/node8plus/plugins/PgMutationCreatePlugin.js.flow +2 -3
- package/node8plus/plugins/PgMutationCreatePlugin.js.map +1 -1
- package/node8plus/plugins/PgMutationPayloadEdgePlugin.js +2 -1
- package/node8plus/plugins/PgMutationPayloadEdgePlugin.js.flow +2 -1
- package/node8plus/plugins/PgMutationPayloadEdgePlugin.js.map +1 -1
- package/node8plus/plugins/PgMutationUpdateDeletePlugin.js +4 -4
- package/node8plus/plugins/PgMutationUpdateDeletePlugin.js.flow +14 -19
- package/node8plus/plugins/PgMutationUpdateDeletePlugin.js.map +1 -1
- package/node8plus/plugins/PgOrderByPrimaryKeyPlugin.js +4 -3
- package/node8plus/plugins/PgOrderByPrimaryKeyPlugin.js.flow +3 -3
- package/node8plus/plugins/PgOrderByPrimaryKeyPlugin.js.map +1 -1
- package/node8plus/plugins/PgOrderComputedColumnsPlugin.js.flow +3 -5
- package/node8plus/plugins/PgOrderComputedColumnsPlugin.js.map +1 -1
- package/node8plus/plugins/PgQueryProceduresPlugin.js +2 -2
- package/node8plus/plugins/PgQueryProceduresPlugin.js.map +1 -1
- package/node8plus/plugins/PgRecordFunctionConnectionPlugin.js.flow +4 -6
- package/node8plus/plugins/PgRecordFunctionConnectionPlugin.js.map +1 -1
- package/node8plus/plugins/PgRecordReturnTypesPlugin.js +3 -3
- package/node8plus/plugins/PgRecordReturnTypesPlugin.js.flow +5 -5
- package/node8plus/plugins/PgRecordReturnTypesPlugin.js.map +1 -1
- package/node8plus/plugins/PgRowByUniqueConstraint.js.flow +2 -3
- package/node8plus/plugins/PgRowByUniqueConstraint.js.map +1 -1
- package/node8plus/plugins/PgRowNode.js.flow +9 -11
- package/node8plus/plugins/PgRowNode.js.map +1 -1
- package/node8plus/plugins/PgTablesPlugin.js +13 -13
- package/node8plus/plugins/PgTablesPlugin.js.flow +8 -13
- package/node8plus/plugins/PgTablesPlugin.js.map +1 -1
- package/node8plus/plugins/PgTypesPlugin.js +7 -7
- package/node8plus/plugins/PgTypesPlugin.js.flow +28 -34
- package/node8plus/plugins/PgTypesPlugin.js.map +1 -1
- package/node8plus/plugins/debugSql.js +1 -1
- package/node8plus/plugins/introspectionQuery.js.map +1 -1
- package/node8plus/plugins/makeProcField.js +5 -5
- package/node8plus/plugins/makeProcField.js.flow +2 -6
- package/node8plus/plugins/makeProcField.js.map +1 -1
- package/node8plus/plugins/pgField.js.flow +2 -4
- package/node8plus/plugins/pgField.js.map +1 -1
- package/node8plus/plugins/viaTemporaryTable.js +2 -2
- package/node8plus/plugins/viaTemporaryTable.js.map +1 -1
- package/node8plus/queryFromResolveDataFactory.js +3 -4
- package/node8plus/queryFromResolveDataFactory.js.flow +474 -464
- package/node8plus/queryFromResolveDataFactory.js.map +1 -1
- package/node8plus/utils.js +1 -1
- package/node8plus/utils.js.flow +1 -1
- package/node8plus/utils.js.map +1 -1
- package/node8plus/withPgClient.js +1 -1
- package/package.json +4 -4
|
@@ -27,363 +27,370 @@ function assertSafeName(name: mixed) {
|
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export default (queryBuilderOptions: QueryBuilderOptions = {}) =>
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
30
|
+
export default (queryBuilderOptions: QueryBuilderOptions = {}) =>
|
|
31
|
+
(
|
|
32
|
+
from: SQL,
|
|
33
|
+
fromAlias: ?SQL,
|
|
34
|
+
resolveData: DataForType,
|
|
35
|
+
inOptions: {
|
|
36
|
+
withPagination?: boolean,
|
|
37
|
+
withPaginationAsFields?: boolean,
|
|
38
|
+
asJson?: boolean,
|
|
39
|
+
asJsonAggregate?: boolean,
|
|
40
|
+
addNullCase?: boolean,
|
|
41
|
+
addNotDistinctFromNullCase?: boolean,
|
|
42
|
+
onlyJsonField?: boolean,
|
|
43
|
+
useAsterisk?: boolean,
|
|
44
|
+
withCursor?: boolean,
|
|
45
|
+
},
|
|
46
|
+
// TODO:v5: context is not optional
|
|
47
|
+
withBuilder?: ((builder: QueryBuilder) => void) | null | void,
|
|
48
|
+
context?: GraphQLContext = {},
|
|
49
|
+
rootValue?: any // eslint-disable-line flowtype/no-weak-types
|
|
50
|
+
) => {
|
|
51
|
+
const {
|
|
52
|
+
pgQuery,
|
|
53
|
+
pgAggregateQuery, // Shorthand for using pgNamedQueryContainer/pgNamedQuery combo
|
|
54
|
+
pgNamedQueryContainer = [],
|
|
55
|
+
pgNamedQuery = [],
|
|
56
|
+
pgCursorPrefix: reallyRawCursorPrefix,
|
|
57
|
+
pgDontUseAsterisk,
|
|
58
|
+
calculateHasNextPage,
|
|
59
|
+
calculateHasPreviousPage,
|
|
60
|
+
usesCursor: explicitlyUsesCursor,
|
|
61
|
+
} = resolveData;
|
|
62
|
+
// Push a query container for aggregates
|
|
63
|
+
if ((pgAggregateQuery && pgAggregateQuery.length) || pgNamedQuery.length) {
|
|
64
|
+
pgNamedQueryContainer.push({
|
|
65
|
+
name: "aggregates",
|
|
66
|
+
query: ({ queryBuilder, options, innerQueryBuilder }) => sql.fragment`\
|
|
66
67
|
(
|
|
67
68
|
select ${innerQueryBuilder.build({ onlyJsonField: true })}
|
|
68
69
|
from ${queryBuilder.getTableExpression()} as ${queryBuilder.getTableAlias()}
|
|
69
70
|
where ${queryBuilder.buildWhereClause(false, false, options)}
|
|
70
71
|
)`,
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
// Convert pgAggregateQuery to pgNamedQueryContainer/pgNamedQuery combo
|
|
75
|
+
if (pgAggregateQuery && pgAggregateQuery.length) {
|
|
76
|
+
// And a query for each previous query
|
|
77
|
+
pgAggregateQuery.forEach(query => {
|
|
78
|
+
pgNamedQuery.push({ name: "aggregates", query });
|
|
79
|
+
});
|
|
80
|
+
}
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
const preventAsterisk = pgDontUseAsterisk
|
|
83
|
+
? pgDontUseAsterisk.length > 0
|
|
84
|
+
: false;
|
|
85
|
+
const options = {
|
|
86
|
+
...inOptions,
|
|
87
|
+
// Allow pgDontUseAsterisk to override useAsterisk
|
|
88
|
+
useAsterisk: inOptions.useAsterisk && !preventAsterisk,
|
|
89
|
+
};
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
const usesCursor: boolean =
|
|
92
|
+
(explicitlyUsesCursor && explicitlyUsesCursor.length > 0) ||
|
|
93
|
+
(calculateHasNextPage && calculateHasNextPage.length > 0) ||
|
|
94
|
+
(calculateHasPreviousPage && calculateHasPreviousPage.length > 0) ||
|
|
95
|
+
false;
|
|
96
|
+
const rawCursorPrefix =
|
|
97
|
+
reallyRawCursorPrefix && reallyRawCursorPrefix.filter(identity);
|
|
97
98
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
const queryBuilder = new QueryBuilder(
|
|
100
|
+
queryBuilderOptions,
|
|
101
|
+
context,
|
|
102
|
+
rootValue
|
|
103
|
+
);
|
|
104
|
+
queryBuilder.from(from, fromAlias ? fromAlias : undefined);
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
106
|
+
if (withBuilder) {
|
|
107
|
+
withBuilder(queryBuilder);
|
|
108
|
+
}
|
|
109
|
+
if (pgQuery) {
|
|
110
|
+
for (let i = 0, l = pgQuery.length; i < l; i++) {
|
|
111
|
+
pgQuery[i](queryBuilder, resolveData);
|
|
112
|
+
}
|
|
111
113
|
}
|
|
112
|
-
}
|
|
113
114
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
115
|
+
function generateNextPrevPageSql(
|
|
116
|
+
sqlQueryAlias,
|
|
117
|
+
canHaveCursorInWhere,
|
|
118
|
+
queryHasBefore,
|
|
119
|
+
queryHasFirst,
|
|
120
|
+
offset = 0,
|
|
121
|
+
invert = false
|
|
122
|
+
) {
|
|
123
|
+
/*
|
|
124
|
+
* Strap in, 'coz this function gets hairy!
|
|
125
|
+
*
|
|
126
|
+
* The point of this function is to return SQL which will resolve to a
|
|
127
|
+
* boolean true/false depending on whether or not there is a (invert ?
|
|
128
|
+
* "previous" : "next") page.
|
|
129
|
+
*
|
|
130
|
+
* Connections have before, after, first, last and offset.
|
|
131
|
+
* - Users are forbidden from setting both first and last.
|
|
132
|
+
* - Users are forbidden from setting both offset and last.
|
|
133
|
+
*
|
|
134
|
+
* Further there are two main modes of paginating, one works by adding a
|
|
135
|
+
* where clause (this is preferred, but is not always possible, and is
|
|
136
|
+
* indicated by `canHaveCursorInWhere === true`) and the other works using
|
|
137
|
+
* standard LIMIT/OFFSET SQL pagination (and is indicated by
|
|
138
|
+
* `canHaveCursorInWhere === false`).
|
|
139
|
+
*
|
|
140
|
+
* The following diagram shows a full collection of records, #, starting at
|
|
141
|
+
* START and ending at END. The key after, before, offset, first and last
|
|
142
|
+
* variables are shown. One thing not show is that it's possible to have
|
|
143
|
+
* fewer records between before and after than requested by first or last.
|
|
144
|
+
* Another detail not clearly show is that if there is no `after` then
|
|
145
|
+
* `START` is used, similarly if there is no `before` then `END` is used.
|
|
146
|
+
*
|
|
147
|
+
* #################################################### < collection
|
|
148
|
+
* ^ ^<-offset->^<-first->^ ^<-last->^ ^
|
|
149
|
+
* | | | | | | |
|
|
150
|
+
* | | +---------+ +--------+ |
|
|
151
|
+
* | | | DATA1 DATA2 | |
|
|
152
|
+
* | | | | |
|
|
153
|
+
* | | | | |
|
|
154
|
+
* | | +-------------------------+ |
|
|
155
|
+
* | | DATA3 | |
|
|
156
|
+
* | after before |
|
|
157
|
+
* | |
|
|
158
|
+
* START END
|
|
159
|
+
*
|
|
160
|
+
* We want one of the three DATA blocks:
|
|
161
|
+
*
|
|
162
|
+
* - If `first` is set, then we want DATA1.
|
|
163
|
+
* - If `last` is set then we want DATA2.
|
|
164
|
+
* - If neither is set then we want DATA3.
|
|
165
|
+
*
|
|
166
|
+
* (Remember: you cannot set both `first` and `last` at the same time.)
|
|
167
|
+
*
|
|
168
|
+
* When invert === false:
|
|
169
|
+
*
|
|
170
|
+
* Note that both DATA2 and DATA3 end at the same point, and we only care
|
|
171
|
+
* if there's data *after* the relevant DATA block, so really we only
|
|
172
|
+
* care if the query specified `first` (`queryHasFirst`) which makes
|
|
173
|
+
* things complex (ending at the end of DATA1), otherwise we can use
|
|
174
|
+
* `before` as the bound (end of DATA2/DATA3).
|
|
175
|
+
*
|
|
176
|
+
* When invert === true:
|
|
177
|
+
*
|
|
178
|
+
* Similarly, DATA1 and DATA3 start at the same point, and we're going
|
|
179
|
+
* backwards so we only care if there's data *before* the DATA block, so
|
|
180
|
+
* really we just need to know if the query set `last` or not, but since
|
|
181
|
+
* this is inverted we call it `queryHasFirst`.
|
|
182
|
+
*
|
|
183
|
+
* When `invert` is false we're calculating `hasNextPage`, when true we're
|
|
184
|
+
* calculating `hasPreviousPage`.
|
|
185
|
+
*
|
|
186
|
+
* Because of the near-symmetry of requesting hasPreviousPage vs
|
|
187
|
+
* hasNextPage we always pretend we're determining `hasNextPage`, and we
|
|
188
|
+
* just invert everything.
|
|
189
|
+
*/
|
|
189
190
|
|
|
190
|
-
|
|
191
|
+
const sqlCommonUnbounded = sql.fragment`\
|
|
191
192
|
select 1
|
|
192
193
|
from ${queryBuilder.getTableExpression()} as ${queryBuilder.getTableAlias()}`;
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
194
|
+
/*
|
|
195
|
+
* This variable is a fragment to go into an `EXISTS(...)` call (after some tweaks).
|
|
196
|
+
*
|
|
197
|
+
* The buildWhereClause takes three arguments:
|
|
198
|
+
*
|
|
199
|
+
* - includeLowerBound (we want this for hasNextPage but not hasPreviousPage)
|
|
200
|
+
* - includeUpperBound (we want this for hasPreviousPage but not hasNextPage)
|
|
201
|
+
* - options (specifically `{addNullCase, addNotDistinctFromNullCase}`) -
|
|
202
|
+
* we just pass this through.
|
|
203
|
+
*
|
|
204
|
+
* So in hasNextPage mode (invert === false), this common SQL ends up
|
|
205
|
+
* representing the collection from `(after || START)` onwards with no
|
|
206
|
+
* upper bound. In hasPreviousPage mode (invert === true), it represents
|
|
207
|
+
* everything from `(before || END)` backwards, with no lower bound.
|
|
208
|
+
*/
|
|
209
|
+
const sqlCommon = sql.fragment`\
|
|
209
210
|
${sqlCommonUnbounded}
|
|
210
211
|
where ${queryBuilder.buildWhereClause(!invert, invert, options)}`;
|
|
211
212
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
213
|
+
/*
|
|
214
|
+
* Since the offset makes the diagram asymmetric, if offset === 0
|
|
215
|
+
* then the diagram is symmetric and things are simplified a little.
|
|
216
|
+
*/
|
|
217
|
+
const isForwardOrSymmetric = !invert || offset === 0;
|
|
217
218
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
assert(isForwardOrSymmetric);
|
|
226
|
-
if (!queryHasBefore && !queryHasFirst) {
|
|
219
|
+
if (!isForwardOrSymmetric) {
|
|
220
|
+
assert(invert);
|
|
221
|
+
assert(offset > 0);
|
|
222
|
+
// We're looking for a previous page, and there's an offset, so lets just
|
|
223
|
+
// assume there's a previous page where offset is smaller.
|
|
224
|
+
return sql.literal(true);
|
|
225
|
+
} else if (canHaveCursorInWhere) {
|
|
227
226
|
assert(isForwardOrSymmetric);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
227
|
+
if (!queryHasBefore && !queryHasFirst) {
|
|
228
|
+
assert(isForwardOrSymmetric);
|
|
229
|
+
// There can be no next page since there's no upper bound
|
|
230
|
+
return sql.literal(false);
|
|
231
|
+
} else if (queryHasBefore && !queryHasFirst) {
|
|
232
|
+
/*
|
|
233
|
+
* We invert the upper buildWhereBoundClause to only represent the data
|
|
234
|
+
* after `before`, then check if there's at least one record in that set.
|
|
235
|
+
*
|
|
236
|
+
* This only works if the `before` cursor can be represented in the
|
|
237
|
+
* SQL WHERE clause, otherwise we're doing limit/offset pagination
|
|
238
|
+
* which requires different logic. It also only works if there's no
|
|
239
|
+
* `first` clause, otherwise there could be a next page before the
|
|
240
|
+
* `before` clause.
|
|
241
|
+
*/
|
|
242
|
+
return sql.fragment`\
|
|
242
243
|
exists(
|
|
243
244
|
${sqlCommonUnbounded}
|
|
244
245
|
where ${queryBuilder.buildWhereClause(false, false, options)}
|
|
245
246
|
and not (${queryBuilder.buildWhereBoundClause(invert)})
|
|
246
247
|
)`;
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
248
|
+
} else {
|
|
249
|
+
assert(queryHasFirst);
|
|
250
|
+
// queryHasBefore could be true or false.
|
|
251
|
+
/*
|
|
252
|
+
* There's a few ways that we could determine if there's a next page.
|
|
253
|
+
*
|
|
254
|
+
* If !queryHasBefore, we could COUNT(*) the number of rows in
|
|
255
|
+
* `sqlCommon` and see if it's larger than `first`:
|
|
256
|
+
* `(select count(*) > ${first} from (${sqlCommon}) __random_table_alias__)`
|
|
257
|
+
*
|
|
258
|
+
* If !queryHasBefore, we could build a subquery table of offsetData
|
|
259
|
+
* from sqlCommon and see if it contains any rows:
|
|
260
|
+
* `EXISTS(select 1 from (${sqlCommon} OFFSET ${first}) __random_table_alias__)`.
|
|
261
|
+
*
|
|
262
|
+
* We could see if there's at least one row in sqlCommon that's not
|
|
263
|
+
* already in our chosen result set.
|
|
264
|
+
*
|
|
265
|
+
* We've chosen the latter approach here because it doesn't place a limit
|
|
266
|
+
* on queryHasBefore.
|
|
267
|
+
*/
|
|
268
|
+
// Drop the `first` limit, see if there are any records that aren't
|
|
269
|
+
// already in the list we've fetched.
|
|
270
|
+
return sql.fragment`\
|
|
270
271
|
exists(
|
|
271
272
|
${sqlCommon}
|
|
272
273
|
and (${queryBuilder.getSelectCursor()})::text not in (select __cursor::text from ${sqlQueryAlias})
|
|
273
274
|
${offset === 0 ? sql.blank : sql.fragment`offset ${sql.value(offset)}`}
|
|
274
275
|
)`;
|
|
275
|
-
|
|
276
|
-
} else {
|
|
277
|
-
assert(!invert || offset === 0); // isForwardOrSymmetric
|
|
278
|
-
assert(!canHaveCursorInWhere);
|
|
279
|
-
// We're dealing with LIMIT/OFFSET pagination here, which means `natural`
|
|
280
|
-
// cursors, so the `queryBuilder` factors the before/after, first/last
|
|
281
|
-
// into the limit / offset.
|
|
282
|
-
const { limit } = queryBuilder.getFinalLimitAndOffset();
|
|
283
|
-
|
|
284
|
-
if (limit == null) {
|
|
285
|
-
// If paginating backwards, then offset > 0 has already been dealt
|
|
286
|
-
// with. Unbounded, so there's no next page.
|
|
287
|
-
return sql.fragment`false`;
|
|
288
|
-
} else if (invert) {
|
|
289
|
-
assert(offset === 0);
|
|
290
|
-
// Paginating backwards and there's no offset (which factors in before/after), so there's no previous page.
|
|
291
|
-
return sql.fragment`false`;
|
|
276
|
+
}
|
|
292
277
|
} else {
|
|
293
|
-
assert(!invert);
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
278
|
+
assert(!invert || offset === 0); // isForwardOrSymmetric
|
|
279
|
+
assert(!canHaveCursorInWhere);
|
|
280
|
+
// We're dealing with LIMIT/OFFSET pagination here, which means `natural`
|
|
281
|
+
// cursors, so the `queryBuilder` factors the before/after, first/last
|
|
282
|
+
// into the limit / offset.
|
|
283
|
+
const { limit } = queryBuilder.getFinalLimitAndOffset();
|
|
284
|
+
|
|
285
|
+
if (limit == null) {
|
|
286
|
+
// If paginating backwards, then offset > 0 has already been dealt
|
|
287
|
+
// with. Unbounded, so there's no next page.
|
|
288
|
+
return sql.fragment`false`;
|
|
289
|
+
} else if (invert) {
|
|
290
|
+
assert(offset === 0);
|
|
291
|
+
// Paginating backwards and there's no offset (which factors in before/after), so there's no previous page.
|
|
292
|
+
return sql.fragment`false`;
|
|
293
|
+
} else {
|
|
294
|
+
assert(!invert);
|
|
295
|
+
/*
|
|
296
|
+
* We're paginating forwards; either there's a before, there's a first,
|
|
297
|
+
* or both.
|
|
298
|
+
*
|
|
299
|
+
* We want to see if there's more than limit+offset records in sqlCommon.
|
|
300
|
+
*/
|
|
301
|
+
return sql.fragment`\
|
|
301
302
|
exists(
|
|
302
303
|
${sqlCommon}
|
|
303
304
|
offset ${sql.literal(limit + offset)}
|
|
304
305
|
)`;
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
const getPgCursorPrefix = () =>
|
|
309
|
-
rawCursorPrefix && rawCursorPrefix.length > 0
|
|
310
|
-
? rawCursorPrefix
|
|
311
|
-
: queryBuilder.data.cursorPrefix.map(val => sql.literal(val));
|
|
312
|
-
if (
|
|
313
|
-
options.withPagination ||
|
|
314
|
-
options.withPaginationAsFields ||
|
|
315
|
-
options.withCursor
|
|
316
|
-
) {
|
|
317
|
-
// Sometimes we need a __cursor even if it's not a collection; e.g. to get the edge field on a mutation
|
|
318
|
-
if (usesCursor) {
|
|
319
|
-
queryBuilder.selectCursor((): SQL => {
|
|
320
|
-
const orderBy = queryBuilder
|
|
321
|
-
.getOrderByExpressionsAndDirections()
|
|
322
|
-
.map(([expr]) => expr);
|
|
323
|
-
if (queryBuilder.isOrderUnique() && orderBy.length > 0) {
|
|
324
|
-
return sql.fragment`json_build_array(${sql.join(
|
|
325
|
-
[
|
|
326
|
-
...getPgCursorPrefix(),
|
|
327
|
-
sql.fragment`json_build_array(${sql.join(orderBy, ", ")})`,
|
|
328
|
-
],
|
|
329
|
-
", "
|
|
330
|
-
)})`;
|
|
331
|
-
} else {
|
|
332
|
-
return sql.fragment`json_build_array(${sql.join(
|
|
333
|
-
getPgCursorPrefix(),
|
|
334
|
-
", "
|
|
335
|
-
)}, ${
|
|
336
|
-
/*
|
|
337
|
-
* NOTE[useAsterisk/row_number]: If we have useAsterisk then the
|
|
338
|
-
* query with limit offset is in a subquery, so our row_number()
|
|
339
|
-
* call doesn't know about it. Here we add the offset back in
|
|
340
|
-
* again. See matching NOTE in QueryBuilder.js.
|
|
341
|
-
*/
|
|
342
|
-
options.useAsterisk
|
|
343
|
-
? sql.fragment`${sql.literal(
|
|
344
|
-
queryBuilder.getFinalOffset() || 0
|
|
345
|
-
)} + `
|
|
346
|
-
: sql.fragment``
|
|
347
|
-
}(row_number() over (partition by 1)))`;
|
|
348
306
|
}
|
|
349
|
-
}
|
|
307
|
+
}
|
|
350
308
|
}
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
309
|
+
const getPgCursorPrefix = () =>
|
|
310
|
+
rawCursorPrefix && rawCursorPrefix.length > 0
|
|
311
|
+
? rawCursorPrefix
|
|
312
|
+
: queryBuilder.data.cursorPrefix.map(val => sql.literal(val));
|
|
313
|
+
if (
|
|
314
|
+
options.withPagination ||
|
|
315
|
+
options.withPaginationAsFields ||
|
|
316
|
+
options.withCursor
|
|
317
|
+
) {
|
|
318
|
+
// Sometimes we need a __cursor even if it's not a collection; e.g. to get the edge field on a mutation
|
|
319
|
+
if (usesCursor) {
|
|
320
|
+
queryBuilder.selectCursor((): SQL => {
|
|
321
|
+
const orderBy = queryBuilder
|
|
322
|
+
.getOrderByExpressionsAndDirections()
|
|
323
|
+
.map(([expr]) => expr);
|
|
324
|
+
if (queryBuilder.isOrderUnique() && orderBy.length > 0) {
|
|
325
|
+
return sql.fragment`json_build_array(${sql.join(
|
|
326
|
+
[
|
|
327
|
+
...getPgCursorPrefix(),
|
|
328
|
+
sql.fragment`json_build_array(${sql.join(orderBy, ", ")})`,
|
|
329
|
+
],
|
|
330
|
+
", "
|
|
331
|
+
)})`;
|
|
332
|
+
} else {
|
|
333
|
+
return sql.fragment`json_build_array(${sql.join(
|
|
334
|
+
getPgCursorPrefix(),
|
|
335
|
+
", "
|
|
336
|
+
)}, ${
|
|
337
|
+
/*
|
|
338
|
+
* NOTE[useAsterisk/row_number]: If we have useAsterisk then the
|
|
339
|
+
* query with limit offset is in a subquery, so our row_number()
|
|
340
|
+
* call doesn't know about it. Here we add the offset back in
|
|
341
|
+
* again. See matching NOTE in QueryBuilder.js.
|
|
342
|
+
*/
|
|
343
|
+
options.useAsterisk
|
|
344
|
+
? sql.fragment`${sql.literal(
|
|
345
|
+
queryBuilder.getFinalOffset() || 0
|
|
346
|
+
)} + `
|
|
347
|
+
: sql.fragment``
|
|
348
|
+
}(row_number() over (partition by 1)))`;
|
|
349
|
+
}
|
|
350
|
+
});
|
|
356
351
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
const rawPrefixes = cursorValue.slice(0, cursorValue.length - 1);
|
|
365
|
-
const rawCursors = cursorValue[cursorValue.length - 1];
|
|
366
|
-
if (rawPrefixes.length !== getPgCursorPrefix().length) {
|
|
367
|
-
badCursor();
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
if (!Array.isArray(rawCursors)) {
|
|
371
|
-
badCursor();
|
|
372
|
-
return;
|
|
352
|
+
}
|
|
353
|
+
if (options.withPagination || options.withPaginationAsFields) {
|
|
354
|
+
queryBuilder.setCursorComparator((cursorValue, isAfter) => {
|
|
355
|
+
function badCursor() {
|
|
356
|
+
queryBuilder.whereBound(sql.fragment`false`, isAfter);
|
|
373
357
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
358
|
+
const orderByExpressionsAndDirections =
|
|
359
|
+
queryBuilder.getOrderByExpressionsAndDirections();
|
|
360
|
+
if (orderByExpressionsAndDirections.length > 0) {
|
|
361
|
+
if (!queryBuilder.isOrderUnique()) {
|
|
362
|
+
throw new Error(
|
|
363
|
+
"The order supplied is not unique, so before/after cursors cannot be used. Please ensure the supplied order includes all the columns from the primary key or a unique constraint."
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
const rawPrefixes = cursorValue.slice(0, cursorValue.length - 1);
|
|
367
|
+
const rawCursors = cursorValue[cursorValue.length - 1];
|
|
368
|
+
if (rawPrefixes.length !== getPgCursorPrefix().length) {
|
|
369
|
+
badCursor();
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
if (!Array.isArray(rawCursors)) {
|
|
373
|
+
badCursor();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
let sqlFilter = sql.fragment`false`;
|
|
377
|
+
const sqlCursors = rawCursors.map(val => sql.value(val));
|
|
378
|
+
for (
|
|
379
|
+
let i = orderByExpressionsAndDirections.length - 1;
|
|
380
|
+
i >= 0;
|
|
381
|
+
i--
|
|
382
|
+
) {
|
|
383
|
+
const [sqlExpression, ascending] =
|
|
384
|
+
orderByExpressionsAndDirections[i];
|
|
385
|
+
// If ascending and isAfter then >
|
|
386
|
+
// If ascending and isBefore then <
|
|
387
|
+
const comparison =
|
|
388
|
+
Number(ascending) ^ Number(!isAfter)
|
|
389
|
+
? sql.fragment`>`
|
|
390
|
+
: sql.fragment`<`;
|
|
384
391
|
|
|
385
|
-
|
|
386
|
-
|
|
392
|
+
const sqlOldFilter = sqlFilter;
|
|
393
|
+
sqlFilter = sql.fragment`\
|
|
387
394
|
(\
|
|
388
395
|
(${sqlExpression} ${comparison} ${sqlCursors[i] || sql.null})
|
|
389
396
|
OR\
|
|
@@ -393,164 +400,167 @@ OR\
|
|
|
393
400
|
${sqlOldFilter}\
|
|
394
401
|
)\
|
|
395
402
|
)`;
|
|
396
|
-
|
|
403
|
+
}
|
|
397
404
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
405
|
+
// Check the cursor prefixes apply
|
|
406
|
+
// TODO:v5: we should be able to do this in JS-land rather than SQL-land
|
|
407
|
+
sqlFilter = sql.fragment`(((${sql.join(
|
|
408
|
+
getPgCursorPrefix(),
|
|
409
|
+
", "
|
|
410
|
+
)}) = (${sql.join(
|
|
411
|
+
rawPrefixes.map(val => sql.value(val)),
|
|
412
|
+
", "
|
|
413
|
+
)})) AND (${sqlFilter}))`;
|
|
414
|
+
queryBuilder.whereBound(sqlFilter, isAfter);
|
|
415
|
+
} else if (
|
|
416
|
+
cursorValue[0] === "natural" &&
|
|
417
|
+
isSafeInteger(cursorValue[1]) &&
|
|
418
|
+
// $FlowFixMe: we know this is a number
|
|
419
|
+
cursorValue[1] >= 0
|
|
420
|
+
) {
|
|
421
|
+
// $FlowFixMe: we know this is a number
|
|
422
|
+
const cursorValue1: number = cursorValue[1];
|
|
423
|
+
if (isAfter) {
|
|
424
|
+
queryBuilder.offset(() => cursorValue1);
|
|
425
|
+
} else {
|
|
426
|
+
queryBuilder.limit(() => {
|
|
427
|
+
const offset = queryBuilder.getOffset();
|
|
428
|
+
return Math.max(0, cursorValue1 - offset - 1);
|
|
429
|
+
});
|
|
430
|
+
}
|
|
418
431
|
} else {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
});
|
|
432
|
+
throw new Error(
|
|
433
|
+
"Cannot use 'before'/'after' without unique 'orderBy'"
|
|
434
|
+
);
|
|
423
435
|
}
|
|
424
|
-
}
|
|
425
|
-
throw new Error("Cannot use 'before'/'after' without unique 'orderBy'");
|
|
426
|
-
}
|
|
427
|
-
});
|
|
436
|
+
});
|
|
428
437
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
438
|
+
const query = queryBuilder.build(options);
|
|
439
|
+
const haveFields = queryBuilder.getSelectFieldsCount() > 0;
|
|
440
|
+
const sqlQueryAlias = sql.identifier(Symbol());
|
|
441
|
+
const sqlSummaryAlias = sql.identifier(Symbol());
|
|
442
|
+
//
|
|
443
|
+
// Tables should ALWAYS push their PK onto the order stack, if this isn't
|
|
444
|
+
// present then we're either dealing with a view or a table without a PK.
|
|
445
|
+
// Either way, we don't have anything to guarantee uniqueness so we need to
|
|
446
|
+
// fall back to limit/offset.
|
|
447
|
+
//
|
|
448
|
+
// TODO: support unique keys in PgAllRows etc
|
|
449
|
+
// TODO: add a warning for cursor-based pagination when using the fallback
|
|
450
|
+
// TODO: if it is a view maybe add a warning encouraging pgViewUniqueKey
|
|
451
|
+
const canHaveCursorInWhere =
|
|
452
|
+
queryBuilder.getOrderByExpressionsAndDirections().length > 0 &&
|
|
453
|
+
queryBuilder.isOrderUnique();
|
|
454
|
+
const queryHasBefore =
|
|
455
|
+
queryBuilder.compiledData.whereBound.upper.length > 0;
|
|
456
|
+
const queryHasAfter =
|
|
457
|
+
queryBuilder.compiledData.whereBound.lower.length > 0;
|
|
458
|
+
const queryHasZeroLimit = queryBuilder.getFinalLimit() === 0;
|
|
459
|
+
const queryHasFirst = isSafeInteger(queryBuilder.compiledData.first);
|
|
460
|
+
const queryHasLast = isSafeInteger(queryBuilder.compiledData.last);
|
|
461
|
+
const hasNextPage = queryHasZeroLimit
|
|
462
|
+
? sql.literal(false)
|
|
463
|
+
: generateNextPrevPageSql(
|
|
464
|
+
sqlQueryAlias,
|
|
465
|
+
canHaveCursorInWhere,
|
|
466
|
+
queryHasBefore,
|
|
467
|
+
queryHasFirst,
|
|
468
|
+
queryBuilder.getFinalOffset() || 0
|
|
469
|
+
);
|
|
470
|
+
const hasPreviousPage = queryHasZeroLimit
|
|
471
|
+
? sql.literal(false)
|
|
472
|
+
: generateNextPrevPageSql(
|
|
473
|
+
sqlQueryAlias,
|
|
474
|
+
canHaveCursorInWhere,
|
|
475
|
+
queryHasAfter,
|
|
476
|
+
queryHasLast,
|
|
477
|
+
queryBuilder.getFinalOffset() || 0,
|
|
478
|
+
true
|
|
479
|
+
);
|
|
470
480
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
484
|
-
if (calculateHasPreviousPage) {
|
|
485
|
-
fields.push([hasPreviousPage, "hasPreviousPage"]);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
if (pgNamedQuery && pgNamedQuery.length) {
|
|
489
|
-
const groups = {};
|
|
490
|
-
pgNamedQuery.forEach(({ name, query }) => {
|
|
491
|
-
assertSafeName(name);
|
|
492
|
-
if (!groups[name]) {
|
|
493
|
-
groups[name] = [];
|
|
481
|
+
const sqlWith = haveFields
|
|
482
|
+
? sql.fragment`with ${sqlQueryAlias} as (${query}), ${sqlSummaryAlias} as (select json_agg(to_json(${sqlQueryAlias})) as data from ${sqlQueryAlias})`
|
|
483
|
+
: sql.fragment``;
|
|
484
|
+
const sqlFrom = sql.fragment``;
|
|
485
|
+
const fields: Array<[SQL, RawAlias]> = [];
|
|
486
|
+
if (haveFields) {
|
|
487
|
+
fields.push([
|
|
488
|
+
sql.fragment`coalesce((select ${sqlSummaryAlias}.data from ${sqlSummaryAlias}), '[]'::json)`,
|
|
489
|
+
"data",
|
|
490
|
+
]);
|
|
491
|
+
if (calculateHasNextPage) {
|
|
492
|
+
fields.push([hasNextPage, "hasNextPage"]);
|
|
494
493
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
494
|
+
if (calculateHasPreviousPage) {
|
|
495
|
+
fields.push([hasPreviousPage, "hasPreviousPage"]);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
if (pgNamedQuery && pgNamedQuery.length) {
|
|
499
|
+
const groups = {};
|
|
500
|
+
pgNamedQuery.forEach(({ name, query }) => {
|
|
501
|
+
assertSafeName(name);
|
|
502
|
+
if (!groups[name]) {
|
|
503
|
+
groups[name] = [];
|
|
504
|
+
}
|
|
505
|
+
groups[name].push(query);
|
|
506
|
+
});
|
|
507
|
+
Object.keys(groups).forEach(groupName => {
|
|
508
|
+
const queryCallbacks = groups[groupName];
|
|
499
509
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
);
|
|
504
|
-
if (containers.length === 0) {
|
|
505
|
-
throw new Error(
|
|
506
|
-
`${queryCallbacks.length} pgNamedQuery entries with name: '${groupName}' existed, but there was no matching pgNamedQueryContainer.`
|
|
510
|
+
// Get container
|
|
511
|
+
const containers = pgNamedQueryContainer.filter(
|
|
512
|
+
c => c.name === groupName
|
|
507
513
|
);
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
514
|
+
if (containers.length === 0) {
|
|
515
|
+
throw new Error(
|
|
516
|
+
`${queryCallbacks.length} pgNamedQuery entries with name: '${groupName}' existed, but there was no matching pgNamedQueryContainer.`
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
if (containers.length > 1) {
|
|
520
|
+
throw new Error(
|
|
521
|
+
`${containers.length} pgNamedQueryContainer entries with name: '${groupName}' existed, but there should be exactly one.`
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
const container = containers[0];
|
|
525
|
+
|
|
526
|
+
const innerQueryBuilder = new QueryBuilder(
|
|
527
|
+
queryBuilderOptions,
|
|
528
|
+
context,
|
|
529
|
+
rootValue
|
|
530
|
+
);
|
|
531
|
+
innerQueryBuilder.from(
|
|
532
|
+
queryBuilder.getTableExpression(),
|
|
533
|
+
queryBuilder.getTableAlias()
|
|
512
534
|
);
|
|
513
|
-
}
|
|
514
|
-
const container = containers[0];
|
|
515
535
|
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
rootValue
|
|
520
|
-
);
|
|
521
|
-
innerQueryBuilder.from(
|
|
522
|
-
queryBuilder.getTableExpression(),
|
|
523
|
-
queryBuilder.getTableAlias()
|
|
524
|
-
);
|
|
536
|
+
for (let i = 0, l = queryCallbacks.length; i < l; i++) {
|
|
537
|
+
queryCallbacks[i](innerQueryBuilder);
|
|
538
|
+
}
|
|
525
539
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
540
|
+
// Generate the SQL statement (e.g. `select ${innerQueryBuilder.build({onlyJsonField: true})} from ${queryBuilder.getTableExpression()} as ...`)
|
|
541
|
+
const aggregatesSql = container.query({
|
|
542
|
+
queryBuilder,
|
|
543
|
+
innerQueryBuilder,
|
|
544
|
+
options,
|
|
545
|
+
});
|
|
529
546
|
|
|
530
|
-
|
|
531
|
-
const aggregatesSql = container.query({
|
|
532
|
-
queryBuilder,
|
|
533
|
-
innerQueryBuilder,
|
|
534
|
-
options,
|
|
547
|
+
fields.push([aggregatesSql, groupName]);
|
|
535
548
|
});
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
549
|
+
}
|
|
550
|
+
if (options.withPaginationAsFields) {
|
|
551
|
+
return sql.fragment`${sqlWith} select ${sql.join(
|
|
552
|
+
fields.map(
|
|
553
|
+
([expr, alias]) => sql.fragment`${expr} as ${sql.identifier(alias)}`
|
|
554
|
+
),
|
|
555
|
+
", "
|
|
556
|
+
)} ${sqlFrom}`;
|
|
557
|
+
} else {
|
|
558
|
+
return sql.fragment`${sqlWith} select ${queryBuilder.jsonbBuildObject(
|
|
559
|
+
fields
|
|
560
|
+
)} ${sqlFrom}`;
|
|
561
|
+
}
|
|
547
562
|
} else {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
)} ${sqlFrom}`;
|
|
563
|
+
const query = queryBuilder.build(options);
|
|
564
|
+
return query;
|
|
551
565
|
}
|
|
552
|
-
}
|
|
553
|
-
const query = queryBuilder.build(options);
|
|
554
|
-
return query;
|
|
555
|
-
}
|
|
556
|
-
};
|
|
566
|
+
};
|