1
- import { isEqual } from 'lodash' ;
2
- import lucene , { AST , BinaryAST , LeftOnlyAST , NodeTerm } from 'lucene' ;
3
-
1
+ import { escapeFilter , escapeFilterValue , concatenate , LuceneQuery } from 'utils/lucene' ;
4
2
import { AdHocVariableFilter } from '@grafana/data' ;
5
3
6
- type ModifierType = '' | '-' ;
7
-
8
- /**
9
- * Checks for the presence of a given label:"value" filter in the query.
10
- */
11
- export function queryHasFilter ( query : string , key : string , value : string , modifier : ModifierType = '' ) : boolean {
12
- return findFilterNode ( query , key , value , modifier ) !== null ;
13
- }
14
-
15
- /**
16
- * Given a query, find the NodeTerm that matches the given field and value.
17
- */
18
- export function findFilterNode (
19
- query : string ,
20
- key : string ,
21
- value : string ,
22
- modifier : ModifierType = ''
23
- ) : NodeTerm | null {
24
- const field = `${ modifier } ${ lucene . term . escape ( key ) } ` ;
25
- value = lucene . phrase . escape ( value ) ;
26
- let ast : AST | null = parseQuery ( query ) ;
27
- if ( ! ast ) {
28
- return null ;
29
- }
30
-
31
- return findNodeInTree ( ast , field , value ) ;
32
- }
33
-
34
- function findNodeInTree ( ast : AST , field : string , value : string ) : NodeTerm | null {
35
- // {}
36
- if ( Object . keys ( ast ) . length === 0 ) {
37
- return null ;
38
- }
39
- // { left: {}, right: {} } or { left: {} }
40
- if ( isAST ( ast . left ) ) {
41
- return findNodeInTree ( ast . left , field , value ) ;
42
- }
43
- if ( isNodeTerm ( ast . left ) && ast . left . field === field && ast . left . term === value ) {
44
- return ast . left ;
45
- }
46
- if ( isLeftOnlyAST ( ast ) ) {
47
- return null ;
48
- }
49
- if ( isNodeTerm ( ast . right ) && ast . right . field === field && ast . right . term === value ) {
50
- return ast . right ;
51
- }
52
- if ( isBinaryAST ( ast . right ) ) {
53
- return findNodeInTree ( ast . right , field , value ) ;
54
- }
55
- return null ;
56
- }
57
-
58
- /**
59
- * Adds a label:"value" expression to the query.
60
- */
61
- export function addFilterToQuery ( query : string , key : string , value : string , modifier : ModifierType = '' ) : string {
62
- if ( queryHasFilter ( query , key , value , modifier ) ) {
63
- return query ;
64
- }
65
-
66
- key = escapeFilter ( key ) ;
67
- value = escapeFilterValue ( value ) ;
68
- const filter = `${ modifier } ${ key } :"${ value } "` ;
69
-
70
- return concatenate ( query , filter ) ;
71
- }
72
-
73
- /**
74
- * Merge a query with a filter.
75
- */
76
- function concatenate ( query : string , filter : string , condition = 'AND' ) : string {
77
- if ( ! filter ) {
78
- return query ;
79
- }
80
- return query . trim ( ) === '' ? filter : `${ query } ${ condition } ${ filter } ` ;
81
- }
82
-
83
4
/**
84
5
* Adds a label:"value" expression to the query.
85
6
*/
@@ -96,7 +17,7 @@ export function addAddHocFilter(query: string, filter: AdHocVariableFilter): str
96
17
97
18
const equalityFilters = [ '=' , '!=' ] ;
98
19
if ( equalityFilters . includes ( filter . operator ) ) {
99
- return addFilterToQuery ( query , filter . key , filter . value , filter . operator === '=' ? '' : '-' ) ;
20
+ return LuceneQuery . parse ( query ) . addFilter ( filter . key , filter . value , filter . operator === '=' ? '' : '-' ) . toString ( ) ;
100
21
}
101
22
/**
102
23
* Keys and values in ad hoc filters may contain characters such as
@@ -121,127 +42,3 @@ export function addAddHocFilter(query: string, filter: AdHocVariableFilter): str
121
42
}
122
43
return concatenate ( query , addHocFilter ) ;
123
44
}
124
-
125
- /**
126
- * Removes a label:"value" expression from the query.
127
- */
128
- export function removeFilterFromQuery ( query : string , key : string , value : string , modifier : ModifierType = '' ) : string {
129
- const node = findFilterNode ( query , key , value , modifier ) ;
130
- const ast = parseQuery ( query ) ;
131
- if ( ! node || ! ast ) {
132
- return query ;
133
- }
134
-
135
- return lucene . toString ( removeNodeFromTree ( ast , node ) ) ;
136
- }
137
-
138
- function removeNodeFromTree ( ast : AST , node : NodeTerm ) : AST {
139
- // {}
140
- if ( Object . keys ( ast ) . length === 0 ) {
141
- return ast ;
142
- }
143
- // { left: {}, right: {} } or { left: {} }
144
- if ( isAST ( ast . left ) ) {
145
- ast . left = removeNodeFromTree ( ast . left , node ) ;
146
- return ast ;
147
- }
148
- if ( isNodeTerm ( ast . left ) && isEqual ( ast . left , node ) ) {
149
- Object . assign (
150
- ast ,
151
- {
152
- left : undefined ,
153
- operator : undefined ,
154
- right : undefined ,
155
- } ,
156
- 'right' in ast ? ast . right : { }
157
- ) ;
158
- return ast ;
159
- }
160
- if ( isLeftOnlyAST ( ast ) ) {
161
- return ast ;
162
- }
163
- if ( isNodeTerm ( ast . right ) && isEqual ( ast . right , node ) ) {
164
- Object . assign ( ast , {
165
- right : undefined ,
166
- operator : undefined ,
167
- } ) ;
168
- return ast ;
169
- }
170
- if ( isBinaryAST ( ast . right ) ) {
171
- ast . right = removeNodeFromTree ( ast . right , node ) ;
172
- return ast ;
173
- }
174
- return ast ;
175
- }
176
-
177
- /**
178
- * Filters can possibly reserved characters such as colons which are part of the Lucene syntax.
179
- * Use this function to escape filter keys.
180
- */
181
- export function escapeFilter ( value : string ) {
182
- return lucene . term . escape ( value ) ;
183
- }
184
-
185
- /**
186
- * Values can possibly reserved special characters such as quotes.
187
- * Use this function to escape filter values.
188
- */
189
- export function escapeFilterValue ( value : string ) {
190
- value = value . replace ( / \\ / g, '\\\\' ) ;
191
- return lucene . phrase . escape ( value ) ;
192
- }
193
-
194
- /**
195
- * Normalizes the query by removing whitespace around colons, which breaks parsing.
196
- */
197
- function normalizeQuery ( query : string ) {
198
- return query . replace ( / ( \w + ) \s ( : ) / gi, '$1$2' ) ;
199
- }
200
-
201
- function isLeftOnlyAST ( ast : unknown ) : ast is LeftOnlyAST {
202
- if ( ! ast || typeof ast !== 'object' ) {
203
- return false ;
204
- }
205
-
206
- if ( 'left' in ast && ! ( 'right' in ast ) ) {
207
- return true ;
208
- }
209
-
210
- return false ;
211
- }
212
-
213
- function isBinaryAST ( ast : unknown ) : ast is BinaryAST {
214
- if ( ! ast || typeof ast !== 'object' ) {
215
- return false ;
216
- }
217
-
218
- if ( 'left' in ast && 'right' in ast ) {
219
- return true ;
220
- }
221
- return false ;
222
- }
223
-
224
- function isAST ( ast : unknown ) : ast is AST {
225
- return isLeftOnlyAST ( ast ) || isBinaryAST ( ast ) ;
226
- }
227
-
228
- function isNodeTerm ( ast : unknown ) : ast is NodeTerm {
229
- if ( ast && typeof ast === 'object' && 'term' in ast ) {
230
- return true ;
231
- }
232
-
233
- return false ;
234
- }
235
-
236
- function parseQuery ( query : string ) {
237
- try {
238
- return lucene . parse ( normalizeQuery ( query ) ) ;
239
- } catch ( e ) {
240
- return null ;
241
- }
242
- }
243
-
244
- export function addStringFilterToQuery ( query : string , filter : string , contains = true ) {
245
- const expression = `"${ escapeFilterValue ( filter ) } "` ;
246
- return query . trim ( ) ? `${ query } ${ contains ? 'AND' : 'NOT' } ${ expression } ` : `${ contains ? '' : 'NOT ' } ${ expression } ` ;
247
- }
0 commit comments