-
Notifications
You must be signed in to change notification settings - Fork 7
Expand file tree
/
Copy pathoperator-factories.ts
More file actions
116 lines (111 loc) · 4.04 KB
/
operator-factories.ts
File metadata and controls
116 lines (111 loc) · 4.04 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* Connection Filter Operator Factories for Search
*
* These factories register filter operators on the connection filter system
* for tsvector (matches) and pg_trgm (similarTo, wordSimilarTo).
*
* They are used in the ConstructivePreset's connectionFilterOperatorFactories
* array to wire search operators into the declarative filter system.
*/
import type { ConnectionFilterOperatorFactory } from 'graphile-connection-filter';
import type { SQL } from 'pg-sql2';
/**
* Creates the `matches` filter operator factory for full-text search.
* Declared here so it's registered via the declarative
* `connectionFilterOperatorFactories` API.
*/
export function createMatchesOperatorFactory(
fullTextScalarName: string,
tsConfig: string
): ConnectionFilterOperatorFactory {
return (build) => {
const { sql, graphql: { GraphQLString } } = build;
const TYPES = build.dataplanPg?.TYPES;
return [{
typeNames: fullTextScalarName,
operatorName: 'matches',
spec: {
description: 'Performs a full text search on the field.',
resolveType: () => GraphQLString,
resolveInputCodec: TYPES ? () => TYPES.text : undefined,
resolve(
sqlIdentifier: SQL,
sqlValue: SQL,
_input: unknown,
_$where: any,
_details: { fieldName: string | null; operatorName: string }
) {
return sql`${sqlIdentifier} @@ websearch_to_tsquery(${sql.literal(tsConfig)}, ${sqlValue})`;
},
},
}];
};
}
/**
* Creates the `similarTo` and `wordSimilarTo` filter operator factories
* for pg_trgm fuzzy text matching. Declared here so they're registered
* via the declarative `connectionFilterOperatorFactories` API.
*
* These operators target 'StringTrgm' (resolved to 'StringTrgmFilter'),
* NOT the global 'String' type. The unified search plugin registers
* 'StringTrgmFilter' and selectively assigns it to string columns on
* tables that qualify for trgm (via intentional search or @trgmSearch tag).
* This prevents trgm operators from appearing on every string field.
*/
export function createTrgmOperatorFactories(): ConnectionFilterOperatorFactory {
return (build) => {
const { sql } = build;
return [
{
typeNames: 'StringTrgm',
operatorName: 'similarTo',
spec: {
description:
'Fuzzy matches using pg_trgm trigram similarity. Tolerates typos and misspellings.',
resolveType: () =>
build.getTypeByName('TrgmSearchInput') as any,
resolve(
sqlIdentifier: SQL,
_sqlValue: SQL,
input: any,
_$where: any,
_details: { fieldName: string | null; operatorName: string }
) {
if (input == null) return null;
const { value, threshold } = input;
if (!value || typeof value !== 'string' || value.trim().length === 0) {
return null;
}
const th = threshold != null ? threshold : 0.3;
return sql`similarity(${sqlIdentifier}, ${sql.value(value)}) > ${sql.value(th)}`;
},
},
},
{
typeNames: 'StringTrgm',
operatorName: 'wordSimilarTo',
spec: {
description:
'Fuzzy matches using pg_trgm word_similarity. Finds the best matching substring within the column value.',
resolveType: () =>
build.getTypeByName('TrgmSearchInput') as any,
resolve(
sqlIdentifier: SQL,
_sqlValue: SQL,
input: any,
_$where: any,
_details: { fieldName: string | null; operatorName: string }
) {
if (input == null) return null;
const { value, threshold } = input;
if (!value || typeof value !== 'string' || value.trim().length === 0) {
return null;
}
const th = threshold != null ? threshold : 0.3;
return sql`word_similarity(${sql.value(value)}, ${sqlIdentifier}) > ${sql.value(th)}`;
},
},
},
];
};
}