@vantreeseba/drizzle-graphql 7.2.0 → 7.3.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 +120 -2
- package/dist/README.md +120 -2
- package/dist/index.cjs +214 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -1
- package/dist/index.d.ts +10 -1
- package/dist/index.js +214 -24
- package/dist/index.js.map +1 -1
- package/dist/package.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -402,6 +402,124 @@ A **to-many** relation takes a `some` / `none` / `every` wrapper
|
|
|
402
402
|
`users(where: { roles: { some: { name: { eq: "admin" } } } })` works the same as a direct
|
|
403
403
|
to-many relation
|
|
404
404
|
|
|
405
|
+
## Matching lists of values
|
|
406
|
+
|
|
407
|
+
`inArray` / `notInArray` take a list of candidate values and compile to SQL `IN` / `NOT IN`.
|
|
408
|
+
Two things about them are worth stating explicitly.
|
|
409
|
+
|
|
410
|
+
**An empty list is answered, not rejected.** A caller building a filter from a variable often
|
|
411
|
+
ends up with an empty list, and that list has a well-defined meaning: nothing is `IN ()` and
|
|
412
|
+
everything is `NOT IN ()`.
|
|
413
|
+
|
|
414
|
+
```graphql
|
|
415
|
+
{
|
|
416
|
+
# no rows
|
|
417
|
+
posts(where: { id: { inArray: [] } }) {
|
|
418
|
+
id
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
# every row
|
|
422
|
+
posts(where: { id: { notInArray: [] } }) {
|
|
423
|
+
id
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
The same rule applies to the Postgres array-column operators: `hasSome: []` matches nothing
|
|
429
|
+
(overlapping with no elements is never true) and `hasEvery: []` matches everything (every
|
|
430
|
+
array contains all zero of them).
|
|
431
|
+
|
|
432
|
+
**The list's own order can be the sort order.** SQL returns `IN` matches in whatever order it
|
|
433
|
+
likes, so a caller that fetches rows by a ranked list of ids normally has to re-sort them
|
|
434
|
+
client-side. Setting `matchFilterOrder` on an `orderBy` entry sorts by the row's position in
|
|
435
|
+
that column's `inArray` list instead of by the column's value:
|
|
436
|
+
|
|
437
|
+
```graphql
|
|
438
|
+
{
|
|
439
|
+
posts(
|
|
440
|
+
where: { id: { inArray: [7, 3, 12] } }
|
|
441
|
+
orderBy: { id: { direction: asc, priority: 1, matchFilterOrder: true } }
|
|
442
|
+
) {
|
|
443
|
+
id # → 7, 3, 12
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
- `direction: asc` keeps the list's order; `desc` reverses it
|
|
449
|
+
- It needs an `inArray` filter on the same column, at the top level of `where` — an
|
|
450
|
+
`inArray` buried inside an `OR` branch raises an error rather than being guessed at
|
|
451
|
+
- It interleaves with the other `orderBy` entries by `priority` like any ordering key, and
|
|
452
|
+
works on relation fields as well as root lists
|
|
453
|
+
- It compiles to a `CASE` ladder over the list values, so it behaves identically on all
|
|
454
|
+
three dialects and every value stays a bound parameter
|
|
455
|
+
- It cannot be combined with `after` or `distinct`: a row's position in the request's own
|
|
456
|
+
filter list is not a value of the row, so it cannot be encoded into a cursor or rebuilt
|
|
457
|
+
against a subquery. Selecting `cursor` under it yields `null` rather than an error
|
|
458
|
+
|
|
459
|
+
## Filtering inside JSON documents
|
|
460
|
+
|
|
461
|
+
`json` / `jsonb` columns get a `path` filter that compares the value at one path inside the
|
|
462
|
+
document, alongside the whole-document `eq` / `ne` / `contains` operators:
|
|
463
|
+
|
|
464
|
+
```graphql
|
|
465
|
+
{
|
|
466
|
+
users(where: { meta: { path: { path: ["profile", "level"], gt: 2 } } }) {
|
|
467
|
+
id
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
`path` walks the document key by key from the root; an all-digits key indexes an array
|
|
473
|
+
(`["tags", "0"]`). Several predicates may be given at once and are `AND`ed:
|
|
474
|
+
|
|
475
|
+
```graphql
|
|
476
|
+
{
|
|
477
|
+
users(
|
|
478
|
+
where: {
|
|
479
|
+
meta: {
|
|
480
|
+
path: [
|
|
481
|
+
{ path: ["profile", "role"], eq: "admin" }
|
|
482
|
+
{ path: ["profile", "level"], gte: 3 }
|
|
483
|
+
]
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
) {
|
|
487
|
+
id
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Available operators are `eq` / `ne` / `lt` / `lte` / `gt` / `gte`, the safe string matchers
|
|
493
|
+
`startsWith` / `endsWith` / `contains` (plus their case-insensitive `i`-prefixed forms), and
|
|
494
|
+
`isNull` / `isNotNull`. A missing key and a JSON `null` both read as null.
|
|
495
|
+
|
|
496
|
+
**How the value is compared** is decided by the operand: a GraphQL number compares
|
|
497
|
+
numerically, a boolean as a boolean, and anything else as text. Pass `as: TEXT | NUMBER |
|
|
498
|
+
BOOLEAN` when the operand's type doesn't match the document's — comparing a numeric field
|
|
499
|
+
against a `String` variable, say:
|
|
500
|
+
|
|
501
|
+
```graphql
|
|
502
|
+
{
|
|
503
|
+
users(where: { meta: { path: { path: ["profile", "level"], as: NUMBER, eq: "3" } } }) {
|
|
504
|
+
id
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
A value of the wrong shape never matches rather than failing the query: `gt: 0` against a
|
|
510
|
+
document whose path holds `"admin"` returns no rows on every dialect. That takes a guard on
|
|
511
|
+
each one — Postgres errors outright on a bad `::numeric`, MySQL quietly casts a non-numeric
|
|
512
|
+
string to `0`, and SQLite's cross-type ordering would otherwise sort every string above every
|
|
513
|
+
number.
|
|
514
|
+
|
|
515
|
+
Note that `contains` means different things at the two levels, because a path names a scalar:
|
|
516
|
+
on the column it is structural JSON containment (Postgres `@>` / MySQL `JSON_CONTAINS`),
|
|
517
|
+
inside a `path` filter it is substring matching on the extracted value.
|
|
518
|
+
|
|
519
|
+
Path filters combine with `AND` / `OR` / `NOT` like any other operator, and are accepted
|
|
520
|
+
everywhere a filter is — list, single and aggregate queries, `update` / `delete`, and the
|
|
521
|
+
`where` argument on a relation field.
|
|
522
|
+
|
|
405
523
|
## Aggregate queries
|
|
406
524
|
|
|
407
525
|
Every table also gets an aggregate query field — `<tableName>Aggregate` (e.g. `usersAggregate`),
|
|
@@ -642,8 +760,8 @@ field, and passing it back as `after` resumes strictly after that row.
|
|
|
642
760
|
- Dates and bigints round-trip losslessly through the cursor
|
|
643
761
|
- A cursor is only valid under the ordering it was issued for — reusing it with a
|
|
644
762
|
different `orderBy` (or a malformed/corrupted cursor) returns a `GraphQLError`
|
|
645
|
-
- `after` cannot be combined with `distinct`, and a table with
|
|
646
|
-
cursor pagination (its `cursor` field resolves to `null`)
|
|
763
|
+
- `after` cannot be combined with `distinct` or with `matchFilterOrder`, and a table with
|
|
764
|
+
no primary key cannot use cursor pagination (its `cursor` field resolves to `null`)
|
|
647
765
|
- If a table has a real column named `cursor`, the column wins and the meta field is
|
|
648
766
|
skipped
|
|
649
767
|
|
package/dist/README.md
CHANGED
|
@@ -402,6 +402,124 @@ A **to-many** relation takes a `some` / `none` / `every` wrapper
|
|
|
402
402
|
`users(where: { roles: { some: { name: { eq: "admin" } } } })` works the same as a direct
|
|
403
403
|
to-many relation
|
|
404
404
|
|
|
405
|
+
## Matching lists of values
|
|
406
|
+
|
|
407
|
+
`inArray` / `notInArray` take a list of candidate values and compile to SQL `IN` / `NOT IN`.
|
|
408
|
+
Two things about them are worth stating explicitly.
|
|
409
|
+
|
|
410
|
+
**An empty list is answered, not rejected.** A caller building a filter from a variable often
|
|
411
|
+
ends up with an empty list, and that list has a well-defined meaning: nothing is `IN ()` and
|
|
412
|
+
everything is `NOT IN ()`.
|
|
413
|
+
|
|
414
|
+
```graphql
|
|
415
|
+
{
|
|
416
|
+
# no rows
|
|
417
|
+
posts(where: { id: { inArray: [] } }) {
|
|
418
|
+
id
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
# every row
|
|
422
|
+
posts(where: { id: { notInArray: [] } }) {
|
|
423
|
+
id
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
The same rule applies to the Postgres array-column operators: `hasSome: []` matches nothing
|
|
429
|
+
(overlapping with no elements is never true) and `hasEvery: []` matches everything (every
|
|
430
|
+
array contains all zero of them).
|
|
431
|
+
|
|
432
|
+
**The list's own order can be the sort order.** SQL returns `IN` matches in whatever order it
|
|
433
|
+
likes, so a caller that fetches rows by a ranked list of ids normally has to re-sort them
|
|
434
|
+
client-side. Setting `matchFilterOrder` on an `orderBy` entry sorts by the row's position in
|
|
435
|
+
that column's `inArray` list instead of by the column's value:
|
|
436
|
+
|
|
437
|
+
```graphql
|
|
438
|
+
{
|
|
439
|
+
posts(
|
|
440
|
+
where: { id: { inArray: [7, 3, 12] } }
|
|
441
|
+
orderBy: { id: { direction: asc, priority: 1, matchFilterOrder: true } }
|
|
442
|
+
) {
|
|
443
|
+
id # → 7, 3, 12
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
- `direction: asc` keeps the list's order; `desc` reverses it
|
|
449
|
+
- It needs an `inArray` filter on the same column, at the top level of `where` — an
|
|
450
|
+
`inArray` buried inside an `OR` branch raises an error rather than being guessed at
|
|
451
|
+
- It interleaves with the other `orderBy` entries by `priority` like any ordering key, and
|
|
452
|
+
works on relation fields as well as root lists
|
|
453
|
+
- It compiles to a `CASE` ladder over the list values, so it behaves identically on all
|
|
454
|
+
three dialects and every value stays a bound parameter
|
|
455
|
+
- It cannot be combined with `after` or `distinct`: a row's position in the request's own
|
|
456
|
+
filter list is not a value of the row, so it cannot be encoded into a cursor or rebuilt
|
|
457
|
+
against a subquery. Selecting `cursor` under it yields `null` rather than an error
|
|
458
|
+
|
|
459
|
+
## Filtering inside JSON documents
|
|
460
|
+
|
|
461
|
+
`json` / `jsonb` columns get a `path` filter that compares the value at one path inside the
|
|
462
|
+
document, alongside the whole-document `eq` / `ne` / `contains` operators:
|
|
463
|
+
|
|
464
|
+
```graphql
|
|
465
|
+
{
|
|
466
|
+
users(where: { meta: { path: { path: ["profile", "level"], gt: 2 } } }) {
|
|
467
|
+
id
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
`path` walks the document key by key from the root; an all-digits key indexes an array
|
|
473
|
+
(`["tags", "0"]`). Several predicates may be given at once and are `AND`ed:
|
|
474
|
+
|
|
475
|
+
```graphql
|
|
476
|
+
{
|
|
477
|
+
users(
|
|
478
|
+
where: {
|
|
479
|
+
meta: {
|
|
480
|
+
path: [
|
|
481
|
+
{ path: ["profile", "role"], eq: "admin" }
|
|
482
|
+
{ path: ["profile", "level"], gte: 3 }
|
|
483
|
+
]
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
) {
|
|
487
|
+
id
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Available operators are `eq` / `ne` / `lt` / `lte` / `gt` / `gte`, the safe string matchers
|
|
493
|
+
`startsWith` / `endsWith` / `contains` (plus their case-insensitive `i`-prefixed forms), and
|
|
494
|
+
`isNull` / `isNotNull`. A missing key and a JSON `null` both read as null.
|
|
495
|
+
|
|
496
|
+
**How the value is compared** is decided by the operand: a GraphQL number compares
|
|
497
|
+
numerically, a boolean as a boolean, and anything else as text. Pass `as: TEXT | NUMBER |
|
|
498
|
+
BOOLEAN` when the operand's type doesn't match the document's — comparing a numeric field
|
|
499
|
+
against a `String` variable, say:
|
|
500
|
+
|
|
501
|
+
```graphql
|
|
502
|
+
{
|
|
503
|
+
users(where: { meta: { path: { path: ["profile", "level"], as: NUMBER, eq: "3" } } }) {
|
|
504
|
+
id
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
A value of the wrong shape never matches rather than failing the query: `gt: 0` against a
|
|
510
|
+
document whose path holds `"admin"` returns no rows on every dialect. That takes a guard on
|
|
511
|
+
each one — Postgres errors outright on a bad `::numeric`, MySQL quietly casts a non-numeric
|
|
512
|
+
string to `0`, and SQLite's cross-type ordering would otherwise sort every string above every
|
|
513
|
+
number.
|
|
514
|
+
|
|
515
|
+
Note that `contains` means different things at the two levels, because a path names a scalar:
|
|
516
|
+
on the column it is structural JSON containment (Postgres `@>` / MySQL `JSON_CONTAINS`),
|
|
517
|
+
inside a `path` filter it is substring matching on the extracted value.
|
|
518
|
+
|
|
519
|
+
Path filters combine with `AND` / `OR` / `NOT` like any other operator, and are accepted
|
|
520
|
+
everywhere a filter is — list, single and aggregate queries, `update` / `delete`, and the
|
|
521
|
+
`where` argument on a relation field.
|
|
522
|
+
|
|
405
523
|
## Aggregate queries
|
|
406
524
|
|
|
407
525
|
Every table also gets an aggregate query field — `<tableName>Aggregate` (e.g. `usersAggregate`),
|
|
@@ -642,8 +760,8 @@ field, and passing it back as `after` resumes strictly after that row.
|
|
|
642
760
|
- Dates and bigints round-trip losslessly through the cursor
|
|
643
761
|
- A cursor is only valid under the ordering it was issued for — reusing it with a
|
|
644
762
|
different `orderBy` (or a malformed/corrupted cursor) returns a `GraphQLError`
|
|
645
|
-
- `after` cannot be combined with `distinct`, and a table with
|
|
646
|
-
cursor pagination (its `cursor` field resolves to `null`)
|
|
763
|
+
- `after` cannot be combined with `distinct` or with `matchFilterOrder`, and a table with
|
|
764
|
+
no primary key cannot use cursor pagination (its `cursor` field resolves to `null`)
|
|
647
765
|
- If a table has a real column named `cursor`, the column wins and the meta field is
|
|
648
766
|
skipped
|
|
649
767
|
|
package/dist/index.cjs
CHANGED
|
@@ -996,10 +996,10 @@ var runMutation = async (db, context, info, txCtx, body) => {
|
|
|
996
996
|
}
|
|
997
997
|
return runOnSharedTx(state, body);
|
|
998
998
|
};
|
|
999
|
-
var batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, orderCtx) => {
|
|
999
|
+
var batchedPaginatedRelationQuery = async (db, targetTable, foreignCol, whereCondition, orderByArg, limit, offset, pkNames, orderCtx, whereArgs) => {
|
|
1000
1000
|
const cols = (0, import_drizzle_orm4.getColumns)(targetTable);
|
|
1001
1001
|
const orderExprs = [
|
|
1002
|
-
...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx) : [],
|
|
1002
|
+
...orderByArg ? extractOrderBy(targetTable, orderByArg, orderCtx, whereArgs) : [],
|
|
1003
1003
|
...primaryKeyOrderExprs(targetTable, pkNames)
|
|
1004
1004
|
];
|
|
1005
1005
|
const orderClause = orderExprs.length ? import_drizzle_orm4.sql` order by ${import_drizzle_orm4.sql.join(orderExprs, import_drizzle_orm4.sql`, `)}` : import_drizzle_orm4.sql``;
|
|
@@ -1110,11 +1110,12 @@ var createRelationResolverFactory = (db, tables, nullOrdering, filterCtx, limits
|
|
|
1110
1110
|
limit ?? null,
|
|
1111
1111
|
offset ?? null,
|
|
1112
1112
|
targetPkNames,
|
|
1113
|
-
relationFilterCtx(filterCtx, targetTableName)
|
|
1113
|
+
relationFilterCtx(filterCtx, targetTableName),
|
|
1114
|
+
whereArg
|
|
1114
1115
|
);
|
|
1115
1116
|
} else {
|
|
1116
1117
|
let q = executor.select().from(targetTable).where(whereCondition);
|
|
1117
|
-
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName)) : [];
|
|
1118
|
+
const orderExprs = cursorEntries ? cursorOrderExprs(targetTable, cursorEntries) : orderByArg ? extractOrderBy(targetTable, orderByArg, relationFilterCtx(filterCtx, targetTableName), whereArg) : [];
|
|
1118
1119
|
if (orderExprs.length) {
|
|
1119
1120
|
q = q.orderBy(...orderExprs);
|
|
1120
1121
|
}
|
|
@@ -1325,6 +1326,10 @@ var innerOrder = new import_graphql5.GraphQLInputObjectType({
|
|
|
1325
1326
|
nulls: {
|
|
1326
1327
|
type: orderNulls,
|
|
1327
1328
|
description: "Where NULL values sort. Defaults to the database's own rule (PostgreSQL: last on asc, first on desc; MySQL/SQLite: first on asc, last on desc)"
|
|
1329
|
+
},
|
|
1330
|
+
matchFilterOrder: {
|
|
1331
|
+
type: import_graphql5.GraphQLBoolean,
|
|
1332
|
+
description: "Sort by this column's position in the `inArray` list the same request's `where` gives it, rather than by the column's own value \u2014 `direction: asc` keeps the list's order, `desc` reverses it. Requires an `inArray` filter on the same column at the top level of `where`, and cannot be combined with `after` or `distinct`."
|
|
1328
1333
|
}
|
|
1329
1334
|
}
|
|
1330
1335
|
});
|
|
@@ -1390,10 +1395,59 @@ var jsonFilterFields = (column, colType) => {
|
|
|
1390
1395
|
description: "Value structurally contains this JSON (Postgres `@>` / MySQL JSON_CONTAINS)"
|
|
1391
1396
|
}
|
|
1392
1397
|
} : {},
|
|
1398
|
+
path: {
|
|
1399
|
+
type: new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(jsonPathFilter)),
|
|
1400
|
+
description: "Compares the value at one path inside the document. Several entries are ANDed; a single object may be passed without the list brackets."
|
|
1401
|
+
},
|
|
1393
1402
|
isNull: { type: import_graphql5.GraphQLBoolean },
|
|
1394
1403
|
isNotNull: { type: import_graphql5.GraphQLBoolean }
|
|
1395
1404
|
};
|
|
1396
1405
|
};
|
|
1406
|
+
var jsonPathCast = new import_graphql5.GraphQLEnumType({
|
|
1407
|
+
name: "JSONPathCast",
|
|
1408
|
+
description: "How to read the value at a JSON path before comparing it",
|
|
1409
|
+
values: {
|
|
1410
|
+
TEXT: { value: "text", description: "Compare as text (lexicographic ordering)" },
|
|
1411
|
+
NUMBER: { value: "number", description: "Compare as a number; a non-numeric value never matches" },
|
|
1412
|
+
BOOLEAN: { value: "boolean", description: "Compare as a boolean" }
|
|
1413
|
+
}
|
|
1414
|
+
});
|
|
1415
|
+
var jsonPathFilter = new import_graphql5.GraphQLInputObjectType({
|
|
1416
|
+
name: "JSONPathFilter",
|
|
1417
|
+
fields: {
|
|
1418
|
+
path: {
|
|
1419
|
+
type: new import_graphql5.GraphQLNonNull(new import_graphql5.GraphQLList(new import_graphql5.GraphQLNonNull(import_graphql5.GraphQLString))),
|
|
1420
|
+
description: 'Keys to walk from the document root, e.g. `["profile", "level"]`. An all-digits key indexes an array.'
|
|
1421
|
+
},
|
|
1422
|
+
as: { type: jsonPathCast, description: "Overrides how the value is read before comparing" },
|
|
1423
|
+
eq: { type: import_graphql_scalars.GraphQLJSON, description: "Equal to" },
|
|
1424
|
+
ne: { type: import_graphql_scalars.GraphQLJSON, description: "Not equal to" },
|
|
1425
|
+
lt: { type: import_graphql_scalars.GraphQLJSON, description: "Less than" },
|
|
1426
|
+
lte: { type: import_graphql_scalars.GraphQLJSON, description: "Less than or equal to" },
|
|
1427
|
+
gt: { type: import_graphql_scalars.GraphQLJSON, description: "Greater than" },
|
|
1428
|
+
gte: { type: import_graphql_scalars.GraphQLJSON, description: "Greater than or equal to" },
|
|
1429
|
+
startsWith: {
|
|
1430
|
+
type: import_graphql5.GraphQLString,
|
|
1431
|
+
description: "Extracted value starts with this string. `%`, `_` and `\\` are matched literally."
|
|
1432
|
+
},
|
|
1433
|
+
endsWith: {
|
|
1434
|
+
type: import_graphql5.GraphQLString,
|
|
1435
|
+
description: "Extracted value ends with this string. `%`, `_` and `\\` are matched literally."
|
|
1436
|
+
},
|
|
1437
|
+
contains: {
|
|
1438
|
+
type: import_graphql5.GraphQLString,
|
|
1439
|
+
description: "Extracted value contains this string. `%`, `_` and `\\` are matched literally."
|
|
1440
|
+
},
|
|
1441
|
+
iStartsWith: { type: import_graphql5.GraphQLString, description: "Case-insensitive `startsWith`." },
|
|
1442
|
+
iEndsWith: { type: import_graphql5.GraphQLString, description: "Case-insensitive `endsWith`." },
|
|
1443
|
+
iContains: { type: import_graphql5.GraphQLString, description: "Case-insensitive `contains`." },
|
|
1444
|
+
isNull: {
|
|
1445
|
+
type: import_graphql5.GraphQLBoolean,
|
|
1446
|
+
description: "When true, matches rows where the path is missing or holds JSON null"
|
|
1447
|
+
},
|
|
1448
|
+
isNotNull: { type: import_graphql5.GraphQLBoolean, description: "When true, matches rows where the path holds a value" }
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1397
1451
|
var IN_ARRAY_DESCRIPTION = "Matches any one of these values (SQL `IN`)";
|
|
1398
1452
|
var NOT_IN_ARRAY_DESCRIPTION = "Matches none of these values (SQL `NOT IN`)";
|
|
1399
1453
|
var arrayFilterFields = (colType, colArr) => {
|
|
@@ -1987,6 +2041,9 @@ var orderByEntries = (orderArgs) => Object.entries(orderArgs).sort((a, b) => (b[
|
|
|
1987
2041
|
if (typeof config === "object" && config.direction === void 0) {
|
|
1988
2042
|
throw new import_graphql5.GraphQLError(`ORDER BY ${column}: ordering through a relation is not supported in this query`);
|
|
1989
2043
|
}
|
|
2044
|
+
if (config.matchFilterOrder) {
|
|
2045
|
+
throw new import_graphql5.GraphQLError(`ORDER BY ${column}: 'matchFilterOrder' is not supported in this query`);
|
|
2046
|
+
}
|
|
1990
2047
|
return [column, config.direction, config.nulls ?? void 0];
|
|
1991
2048
|
});
|
|
1992
2049
|
var orderExpressions = (expr, direction, nulls, dialectColumn) => {
|
|
@@ -2021,7 +2078,22 @@ var extendRelationOrderChain = (parentTable, relationName, relEntry, ctx, chain)
|
|
|
2021
2078
|
table: aliasedTarget
|
|
2022
2079
|
};
|
|
2023
2080
|
};
|
|
2024
|
-
var
|
|
2081
|
+
var filterOrderExpression = (column, columnName, whereArgs) => {
|
|
2082
|
+
const values = whereArgs?.[columnName]?.inArray;
|
|
2083
|
+
if (!Array.isArray(values)) {
|
|
2084
|
+
throw new import_graphql5.GraphQLError(
|
|
2085
|
+
`ORDER BY ${columnName}: 'matchFilterOrder' needs an 'inArray' filter on the same column in this query's 'where'`
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
if (!values.length) {
|
|
2089
|
+
return import_drizzle_orm4.sql`0`;
|
|
2090
|
+
}
|
|
2091
|
+
const branches = values.map(
|
|
2092
|
+
(value, index) => import_drizzle_orm4.sql`when ${column} = ${remapFromGraphQLCore(value, column, columnName)} then ${import_drizzle_orm4.sql.raw(String(index))}`
|
|
2093
|
+
);
|
|
2094
|
+
return import_drizzle_orm4.sql`case ${import_drizzle_orm4.sql.join(branches, import_drizzle_orm4.sql` `)} else ${import_drizzle_orm4.sql.raw(String(values.length))} end`;
|
|
2095
|
+
};
|
|
2096
|
+
var collectOrderEntries = (table, tableKey, orderArgs, ctx, chain, out, whereArgs) => {
|
|
2025
2097
|
const columns = (0, import_drizzle_orm4.getColumns)(table);
|
|
2026
2098
|
const relations = ctx?.relationMap[tableKey];
|
|
2027
2099
|
for (const [key, config] of Object.entries(orderArgs)) {
|
|
@@ -2030,8 +2102,11 @@ var collectOrderEntries = (table, tableKey, orderArgs, ctx, chain, out) => {
|
|
|
2030
2102
|
}
|
|
2031
2103
|
const column = columns[key];
|
|
2032
2104
|
if (column) {
|
|
2105
|
+
if (config.matchFilterOrder && chain) {
|
|
2106
|
+
throw new import_graphql5.GraphQLError(`ORDER BY ${key}: 'matchFilterOrder' is not supported through a relation`);
|
|
2107
|
+
}
|
|
2033
2108
|
out.push({
|
|
2034
|
-
expression: chain ? import_drizzle_orm4.sql`(select ${column} from ${import_drizzle_orm4.sql.join(chain.fromParts, import_drizzle_orm4.sql`, `)} where ${(0, import_drizzle_orm4.and)(...chain.conditions)})` : column,
|
|
2109
|
+
expression: config.matchFilterOrder ? filterOrderExpression(column, key, whereArgs) : chain ? import_drizzle_orm4.sql`(select ${column} from ${import_drizzle_orm4.sql.join(chain.fromParts, import_drizzle_orm4.sql`, `)} where ${(0, import_drizzle_orm4.and)(...chain.conditions)})` : column,
|
|
2035
2110
|
column,
|
|
2036
2111
|
direction: config.direction,
|
|
2037
2112
|
nulls: config.nulls ?? void 0,
|
|
@@ -2044,12 +2119,12 @@ var collectOrderEntries = (table, tableKey, orderArgs, ctx, chain, out) => {
|
|
|
2044
2119
|
throw new import_graphql5.GraphQLError(`ORDER BY ${key}: Unknown column or relation`);
|
|
2045
2120
|
}
|
|
2046
2121
|
const nextChain = extendRelationOrderChain(chain?.table ?? table, key, relEntry, ctx, chain);
|
|
2047
|
-
collectOrderEntries(nextChain.table, relEntry.targetTableName, config, ctx, nextChain, out);
|
|
2122
|
+
collectOrderEntries(nextChain.table, relEntry.targetTableName, config, ctx, nextChain, out, void 0);
|
|
2048
2123
|
}
|
|
2049
2124
|
};
|
|
2050
|
-
var extractOrderBy = (table, orderArgs, ctx) => {
|
|
2125
|
+
var extractOrderBy = (table, orderArgs, ctx, whereArgs) => {
|
|
2051
2126
|
const entries = [];
|
|
2052
|
-
collectOrderEntries(table, ctx?.tableKey ?? "", orderArgs, ctx, void 0, entries);
|
|
2127
|
+
collectOrderEntries(table, ctx?.tableKey ?? "", orderArgs, ctx, void 0, entries, whereArgs);
|
|
2053
2128
|
return entries.sort((a, b) => b.priority - a.priority).flatMap((entry) => orderExpressions(entry.expression, entry.direction, entry.nulls, entry.column));
|
|
2054
2129
|
};
|
|
2055
2130
|
var LIKE_ESCAPE_CHAR = "\\";
|
|
@@ -2081,6 +2156,105 @@ var jsonContains = (column, columnName, value) => {
|
|
|
2081
2156
|
throw new import_graphql5.GraphQLError(`WHERE ${columnName}: Operator 'contains' is not supported for this dialect!`);
|
|
2082
2157
|
}
|
|
2083
2158
|
};
|
|
2159
|
+
var sqlFalse = () => import_drizzle_orm4.sql`1 = 0`;
|
|
2160
|
+
var sqlTrue = () => import_drizzle_orm4.sql`1 = 1`;
|
|
2161
|
+
var jsonPathString = (path) => `$${path.map((part) => /^\d+$/.test(part) ? `[${part}]` : `."${part.replace(/(["\\])/g, "\\$1")}"`).join("")}`;
|
|
2162
|
+
var NUMERIC_TEXT_PATTERN = "^\\s*-?(\\d+\\.?\\d*|\\.\\d+)([eE][-+]?\\d+)?\\s*$";
|
|
2163
|
+
var jsonPathExprs = (column, path) => {
|
|
2164
|
+
switch (columnDialect(column)) {
|
|
2165
|
+
case "pg": {
|
|
2166
|
+
const pathArray = import_drizzle_orm4.sql`array[${import_drizzle_orm4.sql.join(
|
|
2167
|
+
path.map((part) => import_drizzle_orm4.sql`${part}`),
|
|
2168
|
+
import_drizzle_orm4.sql`, `
|
|
2169
|
+
)}]::text[]`;
|
|
2170
|
+
const text = import_drizzle_orm4.sql`(${column} #>> ${pathArray})`;
|
|
2171
|
+
return {
|
|
2172
|
+
text,
|
|
2173
|
+
number: import_drizzle_orm4.sql`(case when ${text} ~ ${NUMERIC_TEXT_PATTERN} then ${text}::numeric end)`,
|
|
2174
|
+
boolean: text,
|
|
2175
|
+
encodeBoolean: (value) => String(value)
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
case "mysql": {
|
|
2179
|
+
const extracted = import_drizzle_orm4.sql`json_extract(${column}, ${jsonPathString(path)})`;
|
|
2180
|
+
const text = import_drizzle_orm4.sql`(case when json_type(${extracted}) = 'NULL' then null else json_unquote(${extracted}) end)`;
|
|
2181
|
+
return {
|
|
2182
|
+
text,
|
|
2183
|
+
number: import_drizzle_orm4.sql`(case when ${text} regexp ${NUMERIC_TEXT_PATTERN} then cast(${text} as decimal(65, 30)) end)`,
|
|
2184
|
+
boolean: text,
|
|
2185
|
+
encodeBoolean: (value) => String(value)
|
|
2186
|
+
};
|
|
2187
|
+
}
|
|
2188
|
+
default: {
|
|
2189
|
+
const extracted = import_drizzle_orm4.sql`json_extract(${column}, ${jsonPathString(path)})`;
|
|
2190
|
+
return {
|
|
2191
|
+
text: import_drizzle_orm4.sql`cast(${extracted} as text)`,
|
|
2192
|
+
number: import_drizzle_orm4.sql`(case when typeof(${extracted}) in ('integer', 'real') then ${extracted} end)`,
|
|
2193
|
+
// SQLite has no boolean type: a JSON `true` comes back as the integer 1.
|
|
2194
|
+
boolean: extracted,
|
|
2195
|
+
encodeBoolean: (value) => value ? 1 : 0
|
|
2196
|
+
};
|
|
2197
|
+
}
|
|
2198
|
+
}
|
|
2199
|
+
};
|
|
2200
|
+
var JSON_PATH_COMPARISONS = {
|
|
2201
|
+
eq: "=",
|
|
2202
|
+
ne: "<>",
|
|
2203
|
+
lt: "<",
|
|
2204
|
+
lte: "<=",
|
|
2205
|
+
gt: ">",
|
|
2206
|
+
gte: ">="
|
|
2207
|
+
};
|
|
2208
|
+
var inferJsonPathCast = (value) => typeof value === "number" ? "number" : typeof value === "boolean" ? "boolean" : "text";
|
|
2209
|
+
var jsonPathCondition = (column, columnName, filter) => {
|
|
2210
|
+
const { path, as: castOverride, ...operators } = filter;
|
|
2211
|
+
const locator = `${columnName}.path`;
|
|
2212
|
+
if (!Array.isArray(path) || !path.length) {
|
|
2213
|
+
throw new import_graphql5.GraphQLError(`WHERE ${locator}: 'path' must name at least one key`);
|
|
2214
|
+
}
|
|
2215
|
+
const exprs = jsonPathExprs(column, path);
|
|
2216
|
+
const variants = [];
|
|
2217
|
+
for (const [operatorName, operatorValue] of Object.entries(operators)) {
|
|
2218
|
+
if (operatorValue === null || operatorValue === void 0) {
|
|
2219
|
+
continue;
|
|
2220
|
+
}
|
|
2221
|
+
if (operatorName === "isNull" || operatorName === "isNotNull") {
|
|
2222
|
+
if (operatorValue === false) {
|
|
2223
|
+
continue;
|
|
2224
|
+
}
|
|
2225
|
+
variants.push(operatorName === "isNull" ? import_drizzle_orm4.sql`${exprs.text} is null` : import_drizzle_orm4.sql`${exprs.text} is not null`);
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
if (operatorName in safeLikeOps) {
|
|
2229
|
+
const { buildPattern, insensitive } = safeLikeOps[operatorName];
|
|
2230
|
+
if (typeof operatorValue !== "string") {
|
|
2231
|
+
throw new import_graphql5.GraphQLError(`WHERE ${locator}: operator '${operatorName}' takes a string`);
|
|
2232
|
+
}
|
|
2233
|
+
variants.push(safeLikeCondition(exprs.text, buildPattern(operatorValue), insensitive));
|
|
2234
|
+
continue;
|
|
2235
|
+
}
|
|
2236
|
+
const comparison = JSON_PATH_COMPARISONS[operatorName];
|
|
2237
|
+
if (!comparison) {
|
|
2238
|
+
throw new import_graphql5.GraphQLError(`WHERE ${locator}: Unknown operator: ${operatorName}`);
|
|
2239
|
+
}
|
|
2240
|
+
const cast = castOverride ?? inferJsonPathCast(operatorValue);
|
|
2241
|
+
if (cast === "number") {
|
|
2242
|
+
const numeric = Number(operatorValue);
|
|
2243
|
+
if (Number.isNaN(numeric)) {
|
|
2244
|
+
throw new import_graphql5.GraphQLError(
|
|
2245
|
+
`WHERE ${locator}: operator '${operatorName}' compares as a number, so its value must be one`
|
|
2246
|
+
);
|
|
2247
|
+
}
|
|
2248
|
+
variants.push(import_drizzle_orm4.sql`${exprs.number} ${import_drizzle_orm4.sql.raw(comparison)} ${numeric}`);
|
|
2249
|
+
} else if (cast === "boolean") {
|
|
2250
|
+
const truthy = operatorValue === true || operatorValue === "true";
|
|
2251
|
+
variants.push(import_drizzle_orm4.sql`${exprs.boolean} ${import_drizzle_orm4.sql.raw(comparison)} ${exprs.encodeBoolean(truthy)}`);
|
|
2252
|
+
} else {
|
|
2253
|
+
variants.push(import_drizzle_orm4.sql`${exprs.text} ${import_drizzle_orm4.sql.raw(comparison)} ${String(operatorValue)}`);
|
|
2254
|
+
}
|
|
2255
|
+
}
|
|
2256
|
+
return variants.length ? variants.length > 1 ? (0, import_drizzle_orm4.and)(...variants) : variants[0] : void 0;
|
|
2257
|
+
};
|
|
2084
2258
|
var extractFiltersColumn = (column, columnName, operators) => {
|
|
2085
2259
|
const { OR, AND, NOT, ...restOperators } = operators;
|
|
2086
2260
|
const entries = Object.entries(restOperators);
|
|
@@ -2102,6 +2276,13 @@ var extractFiltersColumn = (column, columnName, operators) => {
|
|
|
2102
2276
|
variants.push(singleValueOps[operatorName](column, singleValue));
|
|
2103
2277
|
} else if (operatorName in stringValueOps) {
|
|
2104
2278
|
variants.push(stringValueOps[operatorName](column, operatorValue));
|
|
2279
|
+
} else if (operatorName === "path" && isJsonColumn2(column)) {
|
|
2280
|
+
for (const pathFilter of operatorValue) {
|
|
2281
|
+
const extracted = jsonPathCondition(column, columnName, pathFilter);
|
|
2282
|
+
if (extracted) {
|
|
2283
|
+
variants.push(extracted);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2105
2286
|
} else if (operatorName === "contains" && isJsonColumn2(column)) {
|
|
2106
2287
|
variants.push(jsonContains(column, columnName, operatorValue));
|
|
2107
2288
|
} else if (operatorName in safeLikeOps) {
|
|
@@ -2109,7 +2290,8 @@ var extractFiltersColumn = (column, columnName, operators) => {
|
|
|
2109
2290
|
variants.push(safeLikeCondition(column, buildPattern(operatorValue), insensitive));
|
|
2110
2291
|
} else if (operatorName in arrayValueOps) {
|
|
2111
2292
|
if (!operatorValue.length) {
|
|
2112
|
-
|
|
2293
|
+
variants.push(operatorName === "inArray" ? sqlFalse() : sqlTrue());
|
|
2294
|
+
continue;
|
|
2113
2295
|
}
|
|
2114
2296
|
const arrayValue = operatorValue.map((val) => remapFromGraphQLCore(val, column, columnName));
|
|
2115
2297
|
variants.push(arrayValueOps[operatorName](column, arrayValue));
|
|
@@ -2117,7 +2299,8 @@ var extractFiltersColumn = (column, columnName, operators) => {
|
|
|
2117
2299
|
variants.push((0, import_drizzle_orm4.arrayContains)(column, [operatorValue]));
|
|
2118
2300
|
} else if (operatorName in arrayMembershipOps) {
|
|
2119
2301
|
if (!operatorValue.length) {
|
|
2120
|
-
|
|
2302
|
+
variants.push(operatorName === "hasSome" ? sqlFalse() : sqlTrue());
|
|
2303
|
+
continue;
|
|
2121
2304
|
}
|
|
2122
2305
|
variants.push(arrayMembershipOps[operatorName](column, operatorValue));
|
|
2123
2306
|
} else if (operatorName === "isEmpty") {
|
|
@@ -2380,7 +2563,7 @@ var extractRelationsParamsInner = (relationMap, tables, tableName, typeName, ori
|
|
|
2380
2563
|
} : void 0;
|
|
2381
2564
|
const hasPagination = offset != null || limit != null;
|
|
2382
2565
|
const pkNames = targetPkNames ?? [];
|
|
2383
|
-
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName)) : hasPagination && pkNames.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0;
|
|
2566
|
+
thisRecord.orderBy = relationArgs?.orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, relationArgs.orderBy, relationFilterCtx(filterCtx, targetTableName), relWhere) : hasPagination && pkNames.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0;
|
|
2384
2567
|
thisRecord.offset = offset;
|
|
2385
2568
|
thisRecord.limit = limit;
|
|
2386
2569
|
const relWith = relationField ? extractRelationsParamsInner(
|
|
@@ -2460,6 +2643,15 @@ var primaryKeyOrderExprs = (table, pkNames) => {
|
|
|
2460
2643
|
var CURSOR_FIELD_NAME = "cursor";
|
|
2461
2644
|
var ROW_CURSOR_PROP = "__drizzle_graphql_cursor";
|
|
2462
2645
|
var orderByHasRelationEntry = (orderBy) => !!orderBy && Object.values(orderBy).some((config) => config && typeof config === "object" && config.direction === void 0);
|
|
2646
|
+
var orderByCursorObstacle = (orderBy) => {
|
|
2647
|
+
if (orderByHasRelationEntry(orderBy)) {
|
|
2648
|
+
return "'after' cannot be combined with an orderBy that orders through a relation \u2014 a related row's value cannot be encoded into a cursor.";
|
|
2649
|
+
}
|
|
2650
|
+
if (orderBy && Object.values(orderBy).some((config) => config && typeof config === "object" && config.matchFilterOrder)) {
|
|
2651
|
+
return "'after' cannot be combined with 'matchFilterOrder' \u2014 a row's position in the request's own filter list is not a value of the row, so it cannot be encoded into a cursor.";
|
|
2652
|
+
}
|
|
2653
|
+
return void 0;
|
|
2654
|
+
};
|
|
2463
2655
|
var cursorOrderingEntries = (orderBy, pkNames) => {
|
|
2464
2656
|
const entries = orderBy ? orderByEntries(orderBy) : [];
|
|
2465
2657
|
const seen = new Set(entries.map(([column]) => column));
|
|
@@ -3056,11 +3248,10 @@ var runRelationalSelect = async (opts) => {
|
|
|
3056
3248
|
if (after != null && distinct) {
|
|
3057
3249
|
throw new import_graphql5.GraphQLError("'after' cannot be combined with 'distinct'.");
|
|
3058
3250
|
}
|
|
3059
|
-
|
|
3251
|
+
const cursorObstacle = orderByCursorObstacle(orderBy);
|
|
3252
|
+
if (cursorObstacle) {
|
|
3060
3253
|
if (after != null) {
|
|
3061
|
-
throw new import_graphql5.GraphQLError(
|
|
3062
|
-
"'after' cannot be combined with an orderBy that orders through a relation \u2014 a related row's value cannot be encoded into a cursor."
|
|
3063
|
-
);
|
|
3254
|
+
throw new import_graphql5.GraphQLError(cursorObstacle);
|
|
3064
3255
|
}
|
|
3065
3256
|
} else if (!pkNames?.length) {
|
|
3066
3257
|
if (after != null) {
|
|
@@ -3100,13 +3291,13 @@ var runRelationalSelect = async (opts) => {
|
|
|
3100
3291
|
// drizzle-orm v1 RQB calls orderBy/where with the aliased table proxy — use it
|
|
3101
3292
|
// directly so column refs match the CTE alias.
|
|
3102
3293
|
orderBy: distinctKeys ? (aliasedTable2) => [
|
|
3103
|
-
...orderBy ? extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName)) : [],
|
|
3294
|
+
...orderBy ? extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName), where) : [],
|
|
3104
3295
|
...primaryKeyOrderExprs(aliasedTable2, pkNames)
|
|
3105
3296
|
] : cursorEntries ? (
|
|
3106
3297
|
// Cursor pagination needs a total order: the request's orderBy plus the PK tiebreak,
|
|
3107
3298
|
// exactly the ordering the cursor encodes and the keyset predicate compares against.
|
|
3108
3299
|
(aliasedTable2) => cursorOrderExprs(aliasedTable2, cursorEntries)
|
|
3109
|
-
) : orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName)) : needsDefaultOrder && pkNames?.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0,
|
|
3300
|
+
) : orderBy ? (aliasedTable2) => extractOrderBy(aliasedTable2, orderBy, relationFilterCtx(filterCtx, tableName), where) : needsDefaultOrder && pkNames?.length ? (aliasedTable2) => primaryKeyOrderExprs(aliasedTable2, pkNames) : void 0,
|
|
3110
3301
|
where: distinctKeys ? { RAW: (aliasedTable2) => primaryKeyRestriction(aliasedTable2, pkNames, distinctKeys) } : where || cursorValues ? {
|
|
3111
3302
|
RAW: (aliasedTable2) => (0, import_drizzle_orm4.and)(
|
|
3112
3303
|
where ? extractFilters(aliasedTable2, tableName, where, relationFilterCtx(filterCtx, tableName)) : void 0,
|
|
@@ -4763,11 +4954,10 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
4763
4954
|
if (after != null && distinct?.length) {
|
|
4764
4955
|
throw new import_graphql9.GraphQLError("'after' cannot be combined with 'distinct'.");
|
|
4765
4956
|
}
|
|
4766
|
-
|
|
4957
|
+
const cursorObstacle = orderByCursorObstacle(orderBy);
|
|
4958
|
+
if (cursorObstacle) {
|
|
4767
4959
|
if (after != null) {
|
|
4768
|
-
throw new import_graphql9.GraphQLError(
|
|
4769
|
-
"'after' cannot be combined with an orderBy that orders through a relation \u2014 a related row's value cannot be encoded into a cursor."
|
|
4770
|
-
);
|
|
4960
|
+
throw new import_graphql9.GraphQLError(cursorObstacle);
|
|
4771
4961
|
}
|
|
4772
4962
|
} else if (!pkNames.length) {
|
|
4773
4963
|
if (after != null) {
|
|
@@ -4815,7 +5005,7 @@ var generateSelectArray2 = (db, tableName, tables, relationMap, orderArgs, filte
|
|
|
4815
5005
|
q = q.orderBy(...cursorOrderExprs(table, cursorEntries));
|
|
4816
5006
|
} else if (orderBy) {
|
|
4817
5007
|
q = q.orderBy(
|
|
4818
|
-
...extractOrderBy(table, orderBy, relationFilterCtx(filterCtx, tableName)),
|
|
5008
|
+
...extractOrderBy(table, orderBy, relationFilterCtx(filterCtx, tableName), where),
|
|
4819
5009
|
...distinctKeys ? primaryKeyOrderExprs(table, pkNames) : []
|
|
4820
5010
|
);
|
|
4821
5011
|
} else if ((distinctKeys || offset != null || limit != null) && pkNames.length) {
|
|
@@ -4881,7 +5071,7 @@ var generateSelectSingle2 = (db, tableName, tables, relationMap, orderArgs, filt
|
|
|
4881
5071
|
q = q.where(extractFilters(table, tableName, where, relationFilterCtx(filterCtx, tableName)));
|
|
4882
5072
|
}
|
|
4883
5073
|
if (orderBy) {
|
|
4884
|
-
q = q.orderBy(...extractOrderBy(table, orderBy, relationFilterCtx(filterCtx, tableName)));
|
|
5074
|
+
q = q.orderBy(...extractOrderBy(table, orderBy, relationFilterCtx(filterCtx, tableName), where));
|
|
4885
5075
|
} else if (pkNames.length) {
|
|
4886
5076
|
q = q.orderBy(...primaryKeyOrderExprs(table, pkNames));
|
|
4887
5077
|
}
|