Skip to content

Commit b3b0cdd

Browse files
bartovalstratoula
andauthored
[ES|QL] Fix: IN operator inside functions does not display suggestions (elastic#244757)
## Summary The logic only worked if the left operand was a column. Also fixed cases with dot like agent.keyword --------- Co-authored-by: Stratou <[email protected]>
1 parent 3340c42 commit b3b0cdd

File tree

8 files changed

+54
-51
lines changed

8 files changed

+54
-51
lines changed

src/platform/packages/shared/kbn-esql-ast/src/commands_registry/commands/eval/autocomplete.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ describe('EVAL Autocomplete', () => {
203203
});
204204

205205
test('with lists', async () => {
206-
await evalExpectSuggestions('from index | EVAL doubleField in ', ['( $0 )']);
207-
await evalExpectSuggestions('from index | EVAL doubleField not in /', ['( $0 )']);
206+
await evalExpectSuggestions('from index | EVAL doubleField in ', ['($0)']);
207+
await evalExpectSuggestions('from index | EVAL doubleField not in /', ['($0)']);
208208
});
209209

210210
test('after assignment', async () => {

src/platform/packages/shared/kbn-esql-ast/src/commands_registry/commands/where/autocomplete.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,8 @@ describe('WHERE Autocomplete', () => {
280280
});
281281

282282
test('suggestions after IN', async () => {
283-
await whereExpectSuggestions('from index | WHERE doubleField in ', ['( $0 )']);
284-
await whereExpectSuggestions('from index | WHERE doubleField not in ', ['( $0 )']);
283+
await whereExpectSuggestions('from index | WHERE doubleField in ', ['($0)']);
284+
await whereExpectSuggestions('from index | WHERE doubleField not in ', ['($0)']);
285285
const expectedFields = getFieldNamesByType(['double']);
286286
mockFieldsWithTypes(mockCallbacks, expectedFields);
287287
await whereExpectSuggestions(

src/platform/packages/shared/kbn-esql-ast/src/commands_registry/complete_items.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,7 @@ export const semiColonCompleteItem = buildCharCompleteItem(
206206

207207
export const listCompleteItem: ISuggestionItem = withAutoSuggest({
208208
label: '( ... )',
209-
text: '( $0 )',
209+
text: '($0)',
210210
asSnippet: true,
211211
kind: 'Operator',
212212
detail: i18n.translate('kbn-esql-ast.esql.autocomplete.listDoc', {

src/platform/packages/shared/kbn-esql-ast/src/definitions/utils/autocomplete/expressions/operators/handlers.ts

Lines changed: 29 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { getLogicalContinuationSuggestions, shouldSuggestOpenListForOperand } fr
2020
import { shouldSuggestComma } from '../comma_decision_engine';
2121
import { buildConstantsDefinitions } from '../../../literals';
2222
import { SuggestionBuilder } from '../suggestion_builder';
23+
import { withAutoSuggest } from '../../helpers';
2324

2425
// ============================================================================
2526
// eg. IN / NOT IN Operators
@@ -59,60 +60,53 @@ export async function handleListOperator(ctx: ExpressionContext): Promise<ISugge
5960
}
6061
}
6162

62-
if (columnForType) {
63-
// After a value but not after comma: suggest comma
64-
if (
65-
shouldSuggestComma({
66-
position: 'inside_list',
67-
innerText,
68-
listHasValues: list.values && list.values.length > 0,
69-
})
70-
) {
71-
return [{ ...commaCompleteItem, text: ', ' }];
72-
}
73-
74-
// After comma or empty list: suggest values
75-
// For empty lists, don't ignore the left column - we want to suggest fields of the same type
76-
const isEmptyList = !list.values || list.values.length === 0;
77-
const ignoredColumns = isEmptyList
78-
? []
79-
: [
80-
columnForType.parts.join('.'),
81-
...(list.values || []).filter(isColumn).map((col: ESQLColumn) => col.parts.join('.')),
82-
].filter(Boolean);
83-
84-
return getSuggestionsForColumn(columnForType, ctx, ignoredColumns);
63+
// After a value but not after comma: suggest comma
64+
if (
65+
shouldSuggestComma({
66+
position: 'inside_list',
67+
innerText,
68+
listHasValues: list.values && list.values.length > 0,
69+
})
70+
) {
71+
return [withAutoSuggest({ ...commaCompleteItem, text: ',' })];
8572
}
73+
74+
// After comma or empty list: suggest values
75+
const isEmptyList = !list.values || list.values.length === 0;
76+
const ignoredColumns = isEmptyList
77+
? []
78+
: [
79+
...(columnForType ? [columnForType.parts.join('.')] : []),
80+
...(list.values || []).filter(isColumn).map((col: ESQLColumn) => col.parts.join('.')),
81+
].filter(Boolean);
82+
83+
return getListValueSuggestions(ctx, columnForType, ignoredColumns);
8684
}
8785

8886
return [];
8987
}
9088

91-
async function getSuggestionsForColumn(
92-
column: ESQLColumn,
89+
async function getListValueSuggestions(
9390
ctx: ExpressionContext,
91+
column: ESQLColumn | undefined,
9492
ignoredColumns: string[]
9593
): Promise<ISuggestionItem[]> {
96-
const argType = getExpressionType(column, ctx.context?.columns);
97-
98-
if (!argType) {
99-
return [];
100-
}
101-
102-
// Use SuggestionBuilder to eliminate duplicated suggestion patterns
94+
const argType = column ? getExpressionType(column, ctx.context?.columns) : undefined;
10395
const builder = new SuggestionBuilder(ctx);
10496

10597
await builder.addFields({
106-
types: [argType],
98+
types: argType ? [argType] : undefined,
10799
ignoredColumns,
100+
addSpaceAfterField: true,
101+
openSuggestions: true,
108102
});
109103

110104
builder
111105
.addFunctions({
112-
types: [argType],
106+
types: argType ? [argType] : undefined,
113107
})
114108
.addLiterals({
115-
types: [argType],
109+
types: argType ? [argType] : undefined,
116110
includeDateLiterals: true,
117111
includeCompatibleLiterals: true,
118112
});

src/platform/packages/shared/kbn-esql-ast/src/definitions/utils/autocomplete/expressions/operators/partial/handlers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,8 @@ async function handleInfixOperator(
112112
return dispatchOperators({ ...context, innerText: text });
113113
}
114114

115-
const syntheticNode = createSyntheticNode(operatorName, text, expressionRoot);
115+
const leftOperand = expressionRoot?.type === 'column' ? expressionRoot : undefined;
116+
const syntheticNode = createSyntheticNode(operatorName, text, leftOperand);
116117

117118
return dispatchOperators({
118119
...context,

src/platform/packages/shared/kbn-esql-ast/src/definitions/utils/autocomplete/expressions/operators/partial/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ const IS_NOT_REGEX = /\bis\s+not\b/i;
2323

2424
// Regex to extract field name before operator: match[1] = fieldName
2525
// Matches with or without opening parenthesis
26-
const FIELD_BEFORE_IN_REGEX = /(\w+)\s+(?:not\s+)?in\s*\(?\s*$/i;
27-
const FIELD_BEFORE_LIKE_REGEX = /(\w+)\s+(?:not\s+)?(?:r)?like\s*\(?\s*$/i;
26+
const FIELD_BEFORE_IN_REGEX = /([\w.]+)\s+(?:not\s+)?in\s*\(?\s*$/i;
27+
const FIELD_BEFORE_LIKE_REGEX = /([\w.]+)\s+(?:not\s+)?(?:r)?like\s*\(?\s*$/i;
2828

2929
/**
3030
* Creates a synthetic infix operator node (IN, LIKE, RLIKE, etc.).

src/platform/packages/shared/kbn-esql-validation-autocomplete/src/autocomplete/__tests__/functions.test.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -444,27 +444,35 @@ describe('functions arg suggestions', () => {
444444
const suggestions = await suggest('FROM index | EVAL result = CASE(integerField IN /)');
445445
const texts = suggestions.map(({ text }) => text);
446446

447-
expect(texts).toContain('( $0 )');
447+
expect(texts).toContain('($0)');
448448
});
449449

450450
it('NOT IN operator: suggests opening parenthesis for list', async () => {
451451
const { suggest } = await setup();
452452
const suggestions = await suggest('FROM index | EVAL result = CASE(integerField NOT IN /)');
453453
const texts = suggestions.map(({ text }) => text);
454454

455-
expect(texts).toContain('( $0 )');
455+
expect(texts).toContain('($0)');
456456
});
457457

458-
it('IN operator with empty list: suggests integer fields and functions', async () => {
458+
it.each([
459+
['simple field', 'FROM index | WHERE integerField IN (/)'],
460+
['nested field', 'FROM index | WHERE kubernetes.something.something IN (/)'],
461+
['function result', 'FROM index | WHERE CONCAT(textField, keywordField) IN (/)'],
462+
['inside CASE', 'FROM index | EVAL col0 = CASE(keywordField IN (/)'],
463+
[
464+
'multiple IN - cursor on second',
465+
'FROM index | WHERE integerField IN (1) AND keywordField IN (/)',
466+
],
467+
['nested IN inside CASE', 'FROM index | EVAL x = CASE(a IN (1), "yes", keywordField IN (/)'],
468+
])('IN operator with %s: suggests fields and functions', async (_, query) => {
459469
const { suggest } = await setup();
460-
const suggestions = await suggest('FROM index | WHERE integerField IN (/)');
470+
const suggestions = await suggest(query);
461471

462-
const fieldSuggestions = suggestions.filter((s) => s.kind === 'Variable');
463-
const functionSuggestions = suggestions.filter((s) => s.kind === 'Function');
472+
const fieldSuggestions = suggestions.filter(({ kind }) => kind === 'Variable');
464473

465474
expect(suggestions.length).toBeGreaterThan(0);
466475
expect(fieldSuggestions.length).toBeGreaterThan(0);
467-
expect(functionSuggestions.length).toBeGreaterThan(0);
468476
});
469477

470478
it('unary NOT operator in WHERE: suggests boolean fields and boolean-returning functions', async () => {

src/platform/packages/shared/kbn-esql-validation-autocomplete/src/autocomplete/autocomplete.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1045,7 +1045,7 @@ describe('autocomplete', () => {
10451045
});
10461046

10471047
describe('IN operator with lists', () => {
1048-
testSuggestions('FROM a | WHERE integerField IN (doubleField /', [{ text: ', ' }]);
1048+
testSuggestions('FROM a | WHERE integerField IN (doubleField /', [{ text: ',' }]);
10491049
});
10501050

10511051
describe('Replacement ranges are attached when needed', () => {

0 commit comments

Comments
 (0)