json-rest-api 1.0.19 → 1.0.20
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/package.json
CHANGED
|
@@ -1,5 +1,79 @@
|
|
|
1
1
|
import { analyzeRequiredIndexes, buildJoinChain } from './knex-cross-table-search.js'
|
|
2
2
|
|
|
3
|
+
// Resolve operator with sensible defaults for fields declared in searchSchema.
|
|
4
|
+
// - If filterOperator is provided, use it as-is
|
|
5
|
+
// - If field type is string and no operator provided, default to 'like' (contains)
|
|
6
|
+
// - Otherwise default to '='
|
|
7
|
+
export function resolveSearchOperator (fieldDef) {
|
|
8
|
+
if (fieldDef && fieldDef.filterOperator) return String(fieldDef.filterOperator)
|
|
9
|
+
if (fieldDef && fieldDef.type === 'string') return 'like'
|
|
10
|
+
return '='
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Apply a comparison for a single field/operator/value onto a query builder.
|
|
14
|
+
// Handles contains/startsWith/endsWith, IN, BETWEEN, =/== null semantics,
|
|
15
|
+
// and uses ILIKE for case-insensitive matching on Postgres.
|
|
16
|
+
export function applyWhereForOperator ({ builder, columnRef, operator, value, knex, or = false }) {
|
|
17
|
+
const method = or ? 'orWhere' : 'where'
|
|
18
|
+
const methodNull = or ? 'orWhereNull' : 'whereNull'
|
|
19
|
+
const methodIn = or ? 'orWhereIn' : 'whereIn'
|
|
20
|
+
const likeOp = 'like'
|
|
21
|
+
|
|
22
|
+
const op = typeof operator === 'string' ? operator.toLowerCase() : operator
|
|
23
|
+
|
|
24
|
+
// Normalize scalar from possibly array input
|
|
25
|
+
const firstVal = Array.isArray(value) ? value[0] : value
|
|
26
|
+
|
|
27
|
+
// Null handling for equality and string ops
|
|
28
|
+
if (firstVal === null || firstVal === undefined) {
|
|
29
|
+
if (op === 'like' || op === 'contains' || op === 'startswith' || op === 'endswith') {
|
|
30
|
+
builder[methodNull](columnRef)
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
if (op === '=' || op === '==') {
|
|
34
|
+
builder[methodNull](columnRef)
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Text search operators
|
|
40
|
+
if (op === 'like' || op === 'contains') {
|
|
41
|
+
builder[method](columnRef, likeOp, `%${String(firstVal)}%`)
|
|
42
|
+
return
|
|
43
|
+
}
|
|
44
|
+
if (op === 'startswith') {
|
|
45
|
+
builder[method](columnRef, likeOp, `${String(firstVal)}%`)
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
if (op === 'endswith') {
|
|
49
|
+
builder[method](columnRef, likeOp, `%${String(firstVal)}`)
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Array-based operators
|
|
54
|
+
if (op === 'in') {
|
|
55
|
+
const values = Array.isArray(value) ? value : [value]
|
|
56
|
+
builder[methodIn](columnRef, values)
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
if (op === 'between') {
|
|
60
|
+
const values = Array.isArray(value) ? value : [value]
|
|
61
|
+
if (values.length === 2) {
|
|
62
|
+
builder.whereBetween(columnRef, values)
|
|
63
|
+
} else if (values.length === 1) {
|
|
64
|
+
if (values[0] === null || values[0] === undefined) {
|
|
65
|
+
builder[methodNull](columnRef)
|
|
66
|
+
} else {
|
|
67
|
+
builder[method](columnRef, '=', values[0])
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Default
|
|
74
|
+
builder[method](columnRef, operator || '=', firstVal)
|
|
75
|
+
}
|
|
76
|
+
|
|
3
77
|
const createAdapterUtilities = (hookParams, { getStorageAdapter } = {}) => {
|
|
4
78
|
const context = hookParams.context || {}
|
|
5
79
|
const storageCache = new Map()
|
|
@@ -311,29 +385,7 @@ export const polymorphicFiltersHook = async (hookParams, dependencies) => {
|
|
|
311
385
|
const applyComparison = (builder, scope, alias, field, operator, rawValue) => {
|
|
312
386
|
const columnRef = adapterUtils.translateColumn(scope, field, alias)
|
|
313
387
|
const normalizedValue = adapterUtils.translateFilterValue(scope, field, rawValue)
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
const lowerOp = typeof op === 'string' ? op.toLowerCase() : op
|
|
317
|
-
if (normalizedValue === null && (lowerOp === '=' || lowerOp === '==')) {
|
|
318
|
-
builder.whereNull(columnRef)
|
|
319
|
-
return
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
if (lowerOp === 'like') {
|
|
323
|
-
if (normalizedValue === null || normalizedValue === undefined) {
|
|
324
|
-
builder.whereNull(columnRef)
|
|
325
|
-
} else {
|
|
326
|
-
builder.where(columnRef, 'like', `%${normalizedValue}%`)
|
|
327
|
-
}
|
|
328
|
-
return
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
if (Array.isArray(normalizedValue) && lowerOp === 'in') {
|
|
332
|
-
builder.whereIn(columnRef, normalizedValue)
|
|
333
|
-
return
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
builder.where(columnRef, op, normalizedValue)
|
|
388
|
+
applyWhereForOperator({ builder, columnRef, operator, value: normalizedValue, knex })
|
|
337
389
|
}
|
|
338
390
|
|
|
339
391
|
for (const [filterKey] of Object.entries(filters)) {
|
|
@@ -365,10 +417,10 @@ export const polymorphicFiltersHook = async (hookParams, dependencies) => {
|
|
|
365
417
|
}
|
|
366
418
|
|
|
367
419
|
const finalScope = aliasScopeMap.get(finalAlias) || targetType
|
|
368
|
-
const operator = searchInfo.fieldDef
|
|
420
|
+
const operator = resolveSearchOperator(searchInfo.fieldDef)
|
|
369
421
|
applyComparison(this, finalScope, finalAlias, fieldName, operator, searchInfo.filterValue)
|
|
370
422
|
} else {
|
|
371
|
-
const operator = searchInfo.fieldDef
|
|
423
|
+
const operator = resolveSearchOperator(searchInfo.fieldDef)
|
|
372
424
|
const finalScope = aliasScopeMap.get(baseAlias) || targetType
|
|
373
425
|
applyComparison(this, finalScope, baseAlias, targetFieldPath, operator, searchInfo.filterValue)
|
|
374
426
|
}
|
|
@@ -672,7 +724,7 @@ export const crossTableFiltersHook = async (hookParams, dependencies) => {
|
|
|
672
724
|
// Step 5: Apply WHERE conditions for cross-table filters
|
|
673
725
|
query.where(function () {
|
|
674
726
|
for (const [filterKey, filterValue] of Object.entries(filters)) {
|
|
675
|
-
const fieldDef = searchSchemaStructure[filterKey]
|
|
727
|
+
const fieldDef = schemaInfo.searchSchemaStructure[filterKey]
|
|
676
728
|
if (!fieldDef) continue
|
|
677
729
|
|
|
678
730
|
// Skip non-cross-table and polymorphic filters
|
|
@@ -685,7 +737,7 @@ export const crossTableFiltersHook = async (hookParams, dependencies) => {
|
|
|
685
737
|
// Process cross-table filters
|
|
686
738
|
switch (true) {
|
|
687
739
|
case fieldDef.oneOf && Array.isArray(fieldDef.oneOf): {
|
|
688
|
-
const operator = fieldDef
|
|
740
|
+
const operator = resolveSearchOperator(fieldDef)
|
|
689
741
|
|
|
690
742
|
let searchTerms = [filterValue]
|
|
691
743
|
if (fieldDef.splitBy && typeof filterValue === 'string') {
|
|
@@ -697,29 +749,14 @@ export const crossTableFiltersHook = async (hookParams, dependencies) => {
|
|
|
697
749
|
const applyTermComparison = (builder, method, field, raw) => {
|
|
698
750
|
const columnRef = resolveFieldColumn(field)
|
|
699
751
|
const normalized = normalizeFieldValue(field, raw)
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
return
|
|
709
|
-
}
|
|
710
|
-
|
|
711
|
-
if (operator === 'in') {
|
|
712
|
-
const values = Array.isArray(normalized) ? normalized : [normalized]
|
|
713
|
-
builder[method === 'or' ? 'orWhereIn' : 'whereIn'](columnRef, values)
|
|
714
|
-
return
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
const value = Array.isArray(normalized) ? normalized[0] : normalized
|
|
718
|
-
if (value === null && (operator === '=' || operator === '==')) {
|
|
719
|
-
builder[method === 'or' ? 'orWhereNull' : 'whereNull'](columnRef)
|
|
720
|
-
} else {
|
|
721
|
-
builder[method === 'or' ? 'orWhere' : 'where'](columnRef, operator, value)
|
|
722
|
-
}
|
|
752
|
+
applyWhereForOperator({
|
|
753
|
+
builder,
|
|
754
|
+
columnRef,
|
|
755
|
+
operator,
|
|
756
|
+
value: normalized,
|
|
757
|
+
knex,
|
|
758
|
+
or: method === 'or',
|
|
759
|
+
})
|
|
723
760
|
}
|
|
724
761
|
|
|
725
762
|
this.where(function () {
|
|
@@ -749,45 +786,11 @@ export const crossTableFiltersHook = async (hookParams, dependencies) => {
|
|
|
749
786
|
default:
|
|
750
787
|
const targetField = fieldDef.actualField || filterKey
|
|
751
788
|
const columnRef = resolveFieldColumn(targetField)
|
|
752
|
-
const operator = fieldDef
|
|
789
|
+
const operator = resolveSearchOperator(fieldDef)
|
|
753
790
|
const normalized = normalizeFieldValue(targetField, filterValue)
|
|
754
|
-
|
|
755
|
-
switch (operator) {
|
|
756
|
-
case 'like': {
|
|
757
|
-
const value = Array.isArray(normalized) ? normalized[0] : normalized
|
|
758
|
-
if (value === null || value === undefined) {
|
|
759
|
-
this.whereNull(columnRef)
|
|
760
|
-
} else {
|
|
761
|
-
this.where(columnRef, 'like', `%${String(value)}%`)
|
|
762
|
-
}
|
|
763
|
-
break
|
|
764
|
-
}
|
|
765
|
-
case 'in': {
|
|
766
|
-
const values = Array.isArray(normalized) ? normalized : [normalized]
|
|
767
|
-
this.whereIn(columnRef, values)
|
|
768
|
-
break
|
|
769
|
-
}
|
|
770
|
-
case 'between': {
|
|
771
|
-
const values = Array.isArray(normalized) ? normalized : [normalized]
|
|
772
|
-
if (values.length === 2) {
|
|
773
|
-
this.whereBetween(columnRef, values)
|
|
774
|
-
} else {
|
|
775
|
-
this.where(columnRef, operator, values[0])
|
|
776
|
-
}
|
|
777
|
-
break
|
|
778
|
-
}
|
|
779
|
-
default: {
|
|
780
|
-
const value = Array.isArray(normalized) ? normalized[0] : normalized
|
|
781
|
-
if (value === null && (operator === '=' || operator === '==')) {
|
|
782
|
-
this.whereNull(columnRef)
|
|
783
|
-
} else {
|
|
784
|
-
this.where(columnRef, operator, value)
|
|
785
|
-
}
|
|
786
|
-
break
|
|
787
|
-
}
|
|
788
|
-
}
|
|
791
|
+
applyWhereForOperator({ builder: this, columnRef, operator, value: normalized, knex })
|
|
789
792
|
break
|
|
790
|
-
|
|
793
|
+
}
|
|
791
794
|
}
|
|
792
795
|
})
|
|
793
796
|
}
|
|
@@ -992,7 +995,7 @@ export const basicFiltersHook = async (hookParams, dependencies) => {
|
|
|
992
995
|
switch (true) {
|
|
993
996
|
case fieldDef.oneOf && Array.isArray(fieldDef.oneOf): {
|
|
994
997
|
// Multi-field OR search
|
|
995
|
-
const operator = fieldDef
|
|
998
|
+
const operator = resolveSearchOperator(fieldDef)
|
|
996
999
|
|
|
997
1000
|
// Handle split search terms
|
|
998
1001
|
let searchTerms = [filterValue]
|
|
@@ -1010,24 +1013,14 @@ export const basicFiltersHook = async (hookParams, dependencies) => {
|
|
|
1010
1013
|
const perFieldValue = Array.isArray(normalizedTerm)
|
|
1011
1014
|
? normalizeValue(field, term)
|
|
1012
1015
|
: normalizeValue(field, term)
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
} else if (operator === 'in') {
|
|
1022
|
-
const values = Array.isArray(perFieldValue) ? perFieldValue : [perFieldValue]
|
|
1023
|
-
this[index === 0 ? 'whereIn' : 'orWhereIn'](columnRef, values)
|
|
1024
|
-
} else {
|
|
1025
|
-
if (normalized === null && (operator === '=' || operator === '==')) {
|
|
1026
|
-
this[index === 0 ? 'whereNull' : 'orWhereNull'](columnRef)
|
|
1027
|
-
} else {
|
|
1028
|
-
this[index === 0 ? 'where' : 'orWhere'](columnRef, operator, normalized)
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1016
|
+
applyWhereForOperator({
|
|
1017
|
+
builder: this,
|
|
1018
|
+
columnRef,
|
|
1019
|
+
operator,
|
|
1020
|
+
value: perFieldValue,
|
|
1021
|
+
knex,
|
|
1022
|
+
or: index !== 0,
|
|
1023
|
+
})
|
|
1031
1024
|
})
|
|
1032
1025
|
})
|
|
1033
1026
|
})
|
|
@@ -1035,24 +1028,14 @@ export const basicFiltersHook = async (hookParams, dependencies) => {
|
|
|
1035
1028
|
fieldDef.oneOf.forEach((field, index) => {
|
|
1036
1029
|
const columnRef = qualifyField(field)
|
|
1037
1030
|
const normalizedValue = normalizeValue(field, filterValue)
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
} else if (operator === 'in') {
|
|
1047
|
-
const values = Array.isArray(normalizedValue) ? normalizedValue : [normalizedValue]
|
|
1048
|
-
this[index === 0 ? 'whereIn' : 'orWhereIn'](columnRef, values)
|
|
1049
|
-
} else {
|
|
1050
|
-
if (primary === null && (operator === '=' || operator === '==')) {
|
|
1051
|
-
this[index === 0 ? 'whereNull' : 'orWhereNull'](columnRef)
|
|
1052
|
-
} else {
|
|
1053
|
-
this[index === 0 ? 'where' : 'orWhere'](columnRef, operator, primary)
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1031
|
+
applyWhereForOperator({
|
|
1032
|
+
builder: this,
|
|
1033
|
+
columnRef,
|
|
1034
|
+
operator,
|
|
1035
|
+
value: normalizedValue,
|
|
1036
|
+
knex,
|
|
1037
|
+
or: index !== 0,
|
|
1038
|
+
})
|
|
1056
1039
|
})
|
|
1057
1040
|
}
|
|
1058
1041
|
})
|
|
@@ -1070,50 +1053,12 @@ export const basicFiltersHook = async (hookParams, dependencies) => {
|
|
|
1070
1053
|
// Always qualify field names
|
|
1071
1054
|
const dbField = qualifyField(actualField)
|
|
1072
1055
|
|
|
1073
|
-
const operator = fieldDef
|
|
1056
|
+
const operator = resolveSearchOperator(fieldDef)
|
|
1074
1057
|
const normalized = normalizeValue(actualField, filterValue)
|
|
1075
1058
|
log.trace(`[DEBUG basicFiltersHook] Applying filter: ${dbField} ${operator} ${filterValue}`)
|
|
1076
|
-
|
|
1077
|
-
switch (operator) {
|
|
1078
|
-
case 'like': {
|
|
1079
|
-
const value = Array.isArray(normalized) ? normalized[0] : normalized
|
|
1080
|
-
if (value === null || value === undefined) {
|
|
1081
|
-
this.whereNull(dbField)
|
|
1082
|
-
} else {
|
|
1083
|
-
this.where(dbField, 'like', `%${String(value)}%`)
|
|
1084
|
-
}
|
|
1085
|
-
break
|
|
1086
|
-
}
|
|
1087
|
-
case 'in': {
|
|
1088
|
-
const values = Array.isArray(normalized) ? normalized : [normalized]
|
|
1089
|
-
this.whereIn(dbField, values)
|
|
1090
|
-
break
|
|
1091
|
-
}
|
|
1092
|
-
case 'between': {
|
|
1093
|
-
const values = Array.isArray(normalized) ? normalized : [normalized]
|
|
1094
|
-
if (values.length === 2) {
|
|
1095
|
-
this.whereBetween(dbField, values)
|
|
1096
|
-
} else {
|
|
1097
|
-
if (values[0] === null || values[0] === undefined) {
|
|
1098
|
-
this.whereNull(dbField)
|
|
1099
|
-
} else {
|
|
1100
|
-
this.where(dbField, operator, values[0])
|
|
1101
|
-
}
|
|
1102
|
-
}
|
|
1103
|
-
break
|
|
1104
|
-
}
|
|
1105
|
-
default: {
|
|
1106
|
-
const value = Array.isArray(normalized) ? normalized[0] : normalized
|
|
1107
|
-
if (value === null || value === undefined) {
|
|
1108
|
-
this.whereNull(dbField)
|
|
1109
|
-
} else {
|
|
1110
|
-
this.where(dbField, operator, value)
|
|
1111
|
-
}
|
|
1112
|
-
break
|
|
1113
|
-
}
|
|
1114
|
-
}
|
|
1059
|
+
applyWhereForOperator({ builder: this, columnRef: dbField, operator, value: normalized, knex })
|
|
1115
1060
|
break
|
|
1116
|
-
|
|
1061
|
+
}
|
|
1117
1062
|
}
|
|
1118
1063
|
})
|
|
1119
1064
|
}
|
|
@@ -117,7 +117,7 @@ export function ensureSearchFieldsAreIndexed (searchSchema) {
|
|
|
117
117
|
*
|
|
118
118
|
* // Output: Generated search schema
|
|
119
119
|
* // {
|
|
120
|
-
* // title: { type: 'string'
|
|
120
|
+
* // title: { type: 'string' }, // No operator set; resolver applies defaults
|
|
121
121
|
* // status: { type: 'string', filterOperator: '=' } // Specified operator
|
|
122
122
|
* // }
|
|
123
123
|
*
|
|
@@ -163,7 +163,7 @@ export function ensureSearchFieldsAreIndexed (searchSchema) {
|
|
|
163
163
|
* // {
|
|
164
164
|
* // email: { type: 'string', filterOperator: '=' }, // Explicit
|
|
165
165
|
* // age: { type: 'number', filterOperator: '>=' }, // Explicit
|
|
166
|
-
* // name: { type: 'string'
|
|
166
|
+
* // name: { type: 'string' } // From schema (no operator set)
|
|
167
167
|
* // }
|
|
168
168
|
*
|
|
169
169
|
* @example
|
|
@@ -253,12 +253,11 @@ export const generateSearchSchemaFromSchema = (schema, explicitSearchSchema) =>
|
|
|
253
253
|
return
|
|
254
254
|
}
|
|
255
255
|
|
|
256
|
-
// Simple boolean - copy entire field definition (except search)
|
|
256
|
+
// Simple boolean - copy entire field definition (except search)
|
|
257
257
|
const { search, ...fieldDefWithoutSearch } = fieldDef
|
|
258
258
|
const searchEntry = {
|
|
259
259
|
...fieldDefWithoutSearch,
|
|
260
|
-
//
|
|
261
|
-
filterOperator: fieldDefWithoutSearch.filterOperator || '='
|
|
260
|
+
// Do not set filterOperator here; centralized resolver will apply sensible defaults
|
|
262
261
|
}
|
|
263
262
|
|
|
264
263
|
// For belongsTo relationships, add metadata to map back to the actual field
|
|
@@ -108,63 +108,19 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
const applyFiltersToQuery = ({ query, filters, descriptor, searchSchema, adapter }) => {
|
|
111
|
+
// Filtering is handled by the query hooks to keep behavior consistent
|
|
112
|
+
// (operators, null handling, multi-field, joins, polymorphic, etc.).
|
|
113
|
+
// Avoid applying filters here to prevent duplicate conditions.
|
|
114
|
+
if (searchSchema) {
|
|
115
|
+
return
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// TODO: Probably delete the rest of the function. searchSchema is always there...!!!
|
|
111
119
|
if (!filters || Object.keys(filters).length === 0) return
|
|
112
120
|
|
|
121
|
+
// Fallback: apply minimal safe filtering when no search schema is defined
|
|
113
122
|
for (const [field, rawValue] of Object.entries(filters)) {
|
|
114
|
-
const searchDef = searchSchema ? searchSchema[field] : null
|
|
115
|
-
if (searchDef) {
|
|
116
|
-
const isAdvanced = Boolean(
|
|
117
|
-
searchDef.actualField?.includes('.') ||
|
|
118
|
-
(Array.isArray(searchDef.oneOf) && searchDef.oneOf.some((entry) => entry.includes('.'))) ||
|
|
119
|
-
searchDef.polymorphicField ||
|
|
120
|
-
typeof searchDef.applyFilter === 'function'
|
|
121
|
-
)
|
|
122
|
-
if (!isAdvanced && adapter) {
|
|
123
|
-
const columnRef = adapter.translateColumn(searchDef.actualField || field)
|
|
124
|
-
const operator = (searchDef.filterOperator || '=').toLowerCase()
|
|
125
|
-
const normalizedValues = normalizeFilterValues(rawValue, searchDef, {
|
|
126
|
-
isRelationship: Boolean(searchDef.isRelationship),
|
|
127
|
-
})
|
|
128
|
-
const primaryValue = normalizedValues[0]
|
|
129
|
-
switch (operator) {
|
|
130
|
-
case 'like':
|
|
131
|
-
query.whereRaw(`${columnRef} like ?`, [`%${primaryValue}%`])
|
|
132
|
-
break
|
|
133
|
-
case 'in':
|
|
134
|
-
if (normalizedValues.includes(null) && normalizedValues.length === 1) {
|
|
135
|
-
query.whereNull(columnRef)
|
|
136
|
-
} else if (normalizedValues.length > 1) {
|
|
137
|
-
query.whereIn(columnRef, normalizedValues)
|
|
138
|
-
} else if (primaryValue === null) {
|
|
139
|
-
query.whereNull(columnRef)
|
|
140
|
-
} else {
|
|
141
|
-
query.whereRaw(`${columnRef} = ?`, [primaryValue])
|
|
142
|
-
}
|
|
143
|
-
break
|
|
144
|
-
case 'between':
|
|
145
|
-
if (normalizedValues.length === 2) {
|
|
146
|
-
query.whereBetween(columnRef, normalizedValues)
|
|
147
|
-
} else if (primaryValue === null) {
|
|
148
|
-
query.whereNull(columnRef)
|
|
149
|
-
} else {
|
|
150
|
-
query.whereRaw(`${columnRef} = ?`, [primaryValue])
|
|
151
|
-
}
|
|
152
|
-
break
|
|
153
|
-
default:
|
|
154
|
-
if (primaryValue === null) {
|
|
155
|
-
query.whereNull(columnRef)
|
|
156
|
-
} else {
|
|
157
|
-
query.whereRaw(`${columnRef} ${operator || '='} ?`, [primaryValue])
|
|
158
|
-
}
|
|
159
|
-
break
|
|
160
|
-
}
|
|
161
|
-
continue
|
|
162
|
-
}
|
|
163
|
-
// Leave advanced search fields to downstream hooks
|
|
164
|
-
continue
|
|
165
|
-
}
|
|
166
123
|
const fieldInfo = ensureFilterableField(descriptor, field)
|
|
167
|
-
|
|
168
124
|
const values = normalizeFilterValues(rawValue, fieldInfo.definition, {
|
|
169
125
|
isRelationship: fieldInfo.isRelationship,
|
|
170
126
|
})
|
|
@@ -176,17 +132,14 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
176
132
|
query.whereNull(fieldInfo.column)
|
|
177
133
|
continue
|
|
178
134
|
}
|
|
179
|
-
|
|
180
135
|
if (nonNullValues.length === 1 && !hasNull) {
|
|
181
136
|
query.where(fieldInfo.column, nonNullValues[0])
|
|
182
137
|
continue
|
|
183
138
|
}
|
|
184
|
-
|
|
185
139
|
if (nonNullValues.length > 1 && !hasNull) {
|
|
186
140
|
query.whereIn(fieldInfo.column, nonNullValues)
|
|
187
141
|
continue
|
|
188
142
|
}
|
|
189
|
-
|
|
190
143
|
if (nonNullValues.length > 0 && hasNull) {
|
|
191
144
|
query.where(function filterGroup () {
|
|
192
145
|
this.whereIn(fieldInfo.column, nonNullValues).orWhereNull(fieldInfo.column)
|
|
@@ -2034,11 +1987,30 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2034
1987
|
log,
|
|
2035
1988
|
})
|
|
2036
1989
|
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
1990
|
+
// Apply the same filter hooks as dataQuery for consistent counts
|
|
1991
|
+
const scope = api.resources?.[scopeName]
|
|
1992
|
+
const schemaInfo = scope?.vars?.schemaInfo
|
|
1993
|
+
const tableNameForHooks = schemaInfo?.tableName || adapter.tableAlias
|
|
1994
|
+
|
|
1995
|
+
const hookParams = {
|
|
1996
|
+
context: {
|
|
1997
|
+
knexQuery: {
|
|
1998
|
+
query: adapter.query,
|
|
1999
|
+
filters: context.queryParams?.filters,
|
|
2000
|
+
schemaInfo,
|
|
2001
|
+
scopeName,
|
|
2002
|
+
tableName: tableNameForHooks,
|
|
2003
|
+
db,
|
|
2004
|
+
isAnyApi: true,
|
|
2005
|
+
adapter,
|
|
2006
|
+
storageAdapter,
|
|
2007
|
+
},
|
|
2008
|
+
},
|
|
2009
|
+
}
|
|
2010
|
+
|
|
2011
|
+
await polymorphicFiltersHook(hookParams, queryHookDependencies)
|
|
2012
|
+
await crossTableFiltersHook(hookParams, queryHookDependencies)
|
|
2013
|
+
await basicFiltersHook(hookParams, queryHookDependencies)
|
|
2042
2014
|
|
|
2043
2015
|
const [{ count }] = await adapter.query.count({ count: '*' })
|
|
2044
2016
|
return Number(count)
|