Skip to content

Commit 5efbb95

Browse files
committed
feat: Expand to support FIQL syntax
https://harperdb.atlassian.net/browse/STUDIO-451
1 parent f26944c commit 5efbb95

File tree

2 files changed

+130
-49
lines changed

2 files changed

+130
-49
lines changed

src/features/instance/operations/queries/getSearchByConditions.test.ts

Lines changed: 35 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {
2+
parseNumericalComparator,
23
translateBooleanValue,
34
translateColumnFilterToSearchCondition,
4-
translateNumberComparator,
55
translateStringSearchType,
66
translateStringSearchValue,
77
} from '@/features/instance/operations/queries/getSearchByConditions';
@@ -27,31 +27,46 @@ describe('translateStringSearchType', () => {
2727
});
2828
});
2929

30-
describe('translateStringSearchValue', () => {
31-
it('strips start anchor', () => {
32-
expect(translateStringSearchValue(true, 'foo*')).toBe('foo');
33-
});
34-
it('returns as-is with no anchors', () => {
35-
expect(translateStringSearchValue(false, 'foo')).toBe('foo');
30+
describe('parseNumericalComparator', () => {
31+
it('understand greater than equal', () => {
32+
const myExpectations = {
33+
comparator: 'greater_than_equal',
34+
rawNumber: '10',
35+
};
36+
expect(parseNumericalComparator('>= 10')).toEqual(myExpectations);
37+
expect(parseNumericalComparator('gte 10')).toEqual(myExpectations);
38+
expect(parseNumericalComparator('gte=10')).toEqual(myExpectations);
3639
});
37-
});
3840

39-
describe('translateNumberComparator', () => {
40-
it('maps > to greater_than', () => {
41-
expect(translateNumberComparator('>')).toBe('greater_than');
41+
it('supports dates', () => {
42+
const myExpectations = {
43+
comparator: 'greater_than_equal',
44+
rawNumber: '2025-10-01',
45+
};
46+
expect(parseNumericalComparator('>= 2025-10-01')).toEqual(myExpectations);
4247
});
43-
it('maps >= to greater_than_equal', () => {
44-
expect(translateNumberComparator('>=')).toBe('greater_than_equal');
48+
49+
it('throws errors with unknown operators', () => {
50+
expect(() => parseNumericalComparator('add 2')).toThrowError(
51+
'add is not a known operator; please use <, <=, >, >=, ==, or !=',
52+
);
4553
});
46-
it('maps < to less_than', () => {
47-
expect(translateNumberComparator('<')).toBe('less_than');
54+
55+
it('falls back to equals with pure numbers', () => {
56+
const myExpectations = {
57+
comparator: 'equals',
58+
rawNumber: '2025',
59+
};
60+
expect(parseNumericalComparator('2025')).toEqual(myExpectations);
4861
});
49-
it('maps <= to less_than_equal', () => {
50-
expect(translateNumberComparator('<=')).toBe('less_than_equal');
62+
});
63+
64+
describe('translateStringSearchValue', () => {
65+
it('strips start anchor', () => {
66+
expect(translateStringSearchValue(true, 'foo*')).toBe('foo');
5167
});
52-
it('defaults to equals for = and undefined', () => {
53-
expect(translateNumberComparator('=')).toBe('equals');
54-
expect(translateNumberComparator(undefined)).toBe('equals');
68+
it('returns as-is with no anchors', () => {
69+
expect(translateStringSearchValue(false, 'foo')).toBe('foo');
5570
});
5671
});
5772

src/features/instance/operations/queries/getSearchByConditions.ts

Lines changed: 95 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,18 @@ interface GetSearchByConditionsParams extends InstanceClientIdConfig {
1313
}
1414

1515
type Comparator =
16+
| 'between'
17+
// | 'contains' // Turned off for performance reasons until we add warnings for the user.
18+
// | 'ends_with' // Turned off for performance reasons until we add warnings for the user.
19+
| 'eq'
1620
| 'equals'
17-
| 'contains'
18-
| 'starts_with'
19-
| 'ends_with'
2021
| 'greater_than'
2122
| 'greater_than_equal'
2223
| 'less_than'
2324
| 'less_than_equal'
24-
| 'between';
25+
| 'ne'
26+
| 'not_equal'
27+
| 'starts_with';
2528

2629
export interface SearchCondition {
2730
search_attribute: string;
@@ -96,22 +99,23 @@ export function translateColumnFilterToSearchCondition(key: string, value: strin
9699
case 'BigInt':
97100
case 'Long':
98101
case 'Float': {
99-
const comparator = value.match(/^[><=]+/)?.[0];
100-
const rawValue = comparator ? value.slice(comparator.length) : value;
101-
const parsed = attribute.type.includes('Int') ? parseInt(rawValue, 10) : parseFloat(rawValue);
102+
const { comparator, rawNumber } = parseNumericalComparator(value);
103+
const parsed = attribute.type.includes('Int') ? parseInt(rawNumber, 10) : parseFloat(rawNumber);
104+
if (isNaN(parsed)) {
105+
throw new Error(`${rawNumber} does not appear to be a valid number.`);
106+
}
102107
return {
103108
search_attribute: key,
104-
search_type: translateNumberComparator(comparator),
109+
search_type: comparator,
105110
search_value: parsed,
106111
};
107112
}
108113
case 'Date': {
109-
const comparator = value.match(/^[><=]+/)?.[0];
110-
const rawValue = comparator ? value.slice(comparator.length) : value;
111-
const parsed = new Date(rawValue).toISOString();
114+
const { comparator, rawNumber } = parseNumericalComparator(value);
115+
const parsed = new Date(rawNumber).toISOString();
112116
return {
113117
search_attribute: key,
114-
search_type: translateNumberComparator(comparator),
118+
search_type: comparator,
115119
search_value: parsed,
116120
};
117121
}
@@ -134,6 +138,76 @@ export function translateColumnFilterToSearchCondition(key: string, value: strin
134138
}
135139
}
136140

141+
const comparatorNumericalPrefixMappings: Record<string, Comparator> = {
142+
// greater than
143+
'>': 'greater_than',
144+
'g': 'greater_than',
145+
'gt': 'greater_than',
146+
'greater': 'greater_than',
147+
'greaterthan': 'greater_than',
148+
149+
// greater than or equals
150+
'>=': 'greater_than_equal',
151+
'ge': 'greater_than_equal',
152+
'gte': 'greater_than_equal',
153+
'greaterorequal': 'greater_than_equal',
154+
'greaterthanequal': 'greater_than_equal',
155+
'greaterthanorequal': 'greater_than_equal',
156+
157+
// equals
158+
'===': 'equals',
159+
'==': 'eq',
160+
'=': 'eq',
161+
'equals': 'equals',
162+
'equal': 'equals',
163+
'eq': 'eq',
164+
165+
// not equals
166+
'!==': 'not_equal',
167+
'!=': 'ne',
168+
'notequals': 'not_equal',
169+
'notequal': 'not_equal',
170+
'ne': 'ne',
171+
172+
// less than
173+
'<': 'less_than',
174+
'l': 'less_than',
175+
'lt': 'less_than',
176+
'less': 'less_than',
177+
'lessthan': 'less_than',
178+
179+
// less than or equals
180+
'<=': 'less_than_equal',
181+
'lte': 'less_than_equal',
182+
'le': 'less_than_equal',
183+
'lessorequal': 'less_than_equal',
184+
'lessthanequal': 'less_than_equal',
185+
'lessthanorequal': 'less_than_equal',
186+
}
187+
188+
export function parseNumericalComparator(value: string): { comparator: Comparator, rawNumber: string } {
189+
const simpleOperator = value.toLowerCase().trim().match(/^([><=a-z_ ]+)([\d._TZ-]+)$/);
190+
if (simpleOperator) {
191+
const prefix = simpleOperator[1]
192+
.replace(/[_ ]+/g, '')
193+
.replace(/^([a-z]+)=$/g, '$1');
194+
const number = simpleOperator[2];
195+
const mappedComparator = comparatorNumericalPrefixMappings[prefix];
196+
if (!mappedComparator) {
197+
throw new Error(`${prefix} is not a known operator; please use <, <=, >, >=, ==, or !=`);
198+
}
199+
return {
200+
comparator: mappedComparator,
201+
rawNumber: number,
202+
};
203+
}
204+
// TODO: Support between.
205+
return {
206+
comparator: 'equals',
207+
rawNumber: value,
208+
};
209+
}
210+
137211
export function translateStringSearchType(anchorStart: boolean, attribute: InstanceAttribute): Comparator {
138212
if (anchorStart) {
139213
return 'starts_with';
@@ -151,24 +225,16 @@ export function translateStringSearchValue(anchorStart: boolean, value: string)
151225
return value;
152226
}
153227

154-
export function translateNumberComparator(comparator: string | undefined): Comparator {
155-
switch (comparator) {
156-
case '>':
157-
return 'greater_than';
158-
case '>=':
159-
return 'greater_than_equal';
160-
case '<':
161-
return 'less_than';
162-
case '<=':
163-
return 'less_than_equal';
164-
default:
165-
return 'equals';
166-
}
167-
168-
}
169-
170228
const acceptedOKValues = [
171-
'true', 'yes', 'ok', 'yup', '1', 'si', 'bet', 'tru',
229+
'1',
230+
'bet',
231+
'k',
232+
'ok',
233+
'si',
234+
'tru',
235+
'true',
236+
'yes',
237+
'yup',
172238
];
173239

174240
export function translateBooleanValue(value: string): boolean {

0 commit comments

Comments
 (0)