Skip to content

Commit 3f2b1fb

Browse files
momovdgstratoula
andauthored
[ES|QL] tsdb compatible hint - autocomplete + validation (elastic#270418)
resolves elastic#269963 ## Summary Respects the `tsdb_compatible: false` hint added to ES|QL function metadata ([elasticsearch#149401](elastic/elasticsearch#149401)) by filtering incompatible functions from autocomplete suggestions and raising a validation error when they are used in a time series (`TS`) pipeline. **Function definition generation** — The function definition script needed to propagate the new flag from the elasticsearch defintions. **Autocomplete** — `filterFunctionDefinitions` (the single chokepoint all function-suggestion paths route through) now drops functions with `tsdbCompatible: false` when the query source is `TS`. The TS context is derived once from the parsed AST via the existing `isTimeseriesSourceCommand` helper and carried on `ICommandContext`, so no per-command changes are needed. **Validation** — `FunctionValidator.validate()` reports a new `tsdbIncompatibleFunction` error ("Function X is not supported in time series (TS) pipelines"). The only function carrying `tsdbCompatible: false` today is `SPARKLINE`, but the plumbing is in place for any future additions from upstream. ### Checklist Check the PR satisfies following conditions. Reviewers should verify this PR satisfies this list as well. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios - [x] The PR description includes the appropriate Release Notes section, and the correct `release_note:*` label is applied per the [guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) - [x] Review the [backport guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing) and apply applicable `backport:*` labels. --------- Co-authored-by: Stratou <efstratia.kalafateli@elastic.co>
1 parent dd5781a commit 3f2b1fb

11 files changed

Lines changed: 116 additions & 3 deletions

File tree

src/platform/packages/shared/kbn-esql-language/scripts/generate_function_definitions.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ function getFunctionDefinition(ESFunctionDefinition: Record<string, any>): Funct
8484
alias: aliasTable[ESFunctionDefinition.name],
8585
ignoreAsSuggestion: ESFunctionDefinition.snapshot_only,
8686
preview: ESFunctionDefinition.preview,
87+
tsdbCompatible: ESFunctionDefinition.tsdb_compatible,
8788
signatures: _.uniqBy(
8889
ESFunctionDefinition.signatures.map((signature: any) => ({
8990
...signature,
@@ -332,7 +333,9 @@ const ${getDefinitionName(name)}: FunctionDefinition = {
332333
name: EsqlFunctionNames.${name.toUpperCase()},
333334
description: i18n.translate('kbn-esql-language.esql.definitions.${name}', { defaultMessage: ${JSON.stringify(
334335
removeAsciiDocInternalCrossReferences(removeInlineAsciiDocLinks(description), functionNames)
335-
)} }),${functionDefinition.ignoreAsSuggestion ? 'ignoreAsSuggestion: true,' : ''}
336+
)} }),${functionDefinition.ignoreAsSuggestion ? 'ignoreAsSuggestion: true,' : ''}${
337+
functionDefinition.tsdbCompatible === false ? '\n tsdbCompatible: false,' : ''
338+
}
336339
preview: ${functionDefinition.preview || 'false'},
337340
alias: ${alias ? `['${alias.join("', '")}']` : 'undefined'},
338341
signatures: ${JSON.stringify(signaturesWithLicenseLowerCase, null, 2)},

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/generated/aggregation_functions.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4241,6 +4241,7 @@ const sparklineDefinition: FunctionDefinition = {
42414241
defaultMessage:
42424242
'The values representing the y-axis values of a sparkline graph for a given aggregation over a period of time.',
42434243
}),
4244+
tsdbCompatible: false,
42444245
preview: true,
42454246
alias: undefined,
42464247
signatures: [

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,7 @@ export interface FunctionDefinition {
185185
type: FunctionDefinitionTypes;
186186
preview?: boolean;
187187
ignoreAsSuggestion?: boolean;
188+
tsdbCompatible?: boolean;
188189
name: string;
189190
alias?: string[];
190191
description: string;
@@ -202,6 +203,7 @@ export interface FunctionFilterPredicates {
202203
returnTypes?: string[];
203204
ignored?: string[];
204205
allowed?: string[];
206+
isTimeseriesSource?: boolean;
205207
}
206208

207209
// PromQL Function Definition Types
@@ -490,6 +492,10 @@ export interface ValidationErrors {
490492
message: string;
491493
type: { type: string };
492494
};
495+
tsdbIncompatibleFunction: {
496+
message: string;
497+
type: { fnName: string };
498+
};
493499
}
494500

495501
export type ErrorTypes = keyof ValidationErrors;

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/utils/autocomplete/helpers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ export function getFunctionsSuggestions({
141141
location,
142142
returnTypes: types,
143143
ignored,
144+
isTimeseriesSource: context?.isTimeseriesSource,
144145
};
145146

146147
const hasMinimumLicenseRequired = callbacks?.hasMinimumLicenseRequired;

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/utils/errors.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,13 @@ Expected one of:
497497
}),
498498
type: 'error',
499499
};
500+
case 'tsdbIncompatibleFunction':
501+
return {
502+
message: i18n.translate('kbn-esql-language.esql.validation.tsdbIncompatibleFunction', {
503+
defaultMessage: 'Function {fnName} is not supported in time series (TS) pipelines',
504+
values: { fnName: out.fnName.toUpperCase() },
505+
}),
506+
};
500507
}
501508
return { message: '' };
502509
}
@@ -702,6 +709,9 @@ export const errors = {
702709
locationName,
703710
}),
704711

712+
tsdbIncompatibleFunction: (fn: ESQLFunction): ESQLMessage =>
713+
errors.byId('tsdbIncompatibleFunction', fn.location, { fnName: fn.name }),
714+
705715
wrongNumberArgs: (fn: ESQLFunction, definition: FunctionDefinition): ESQLMessage => {
706716
const validArgCounts = new Set<number>();
707717
let minParams: number | undefined;

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/utils/functions.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,14 +105,26 @@ export const filterFunctionDefinitions = (
105105
if (!predicates) {
106106
return functions;
107107
}
108-
const { location, returnTypes, ignored = [], allowed = [] } = predicates;
108+
const { location, returnTypes, ignored = [], allowed = [], isTimeseriesSource } = predicates;
109109

110110
return functions.filter(
111-
({ name, locationsAvailable, ignoreAsSuggestion, signatures, license, observabilityTier }) => {
111+
({
112+
name,
113+
locationsAvailable,
114+
ignoreAsSuggestion,
115+
tsdbCompatible,
116+
signatures,
117+
license,
118+
observabilityTier,
119+
}) => {
112120
if (ignoreAsSuggestion) {
113121
return false;
114122
}
115123

124+
if (isTimeseriesSource && tsdbCompatible === false) {
125+
return false;
126+
}
127+
116128
if (!!hasMinimumLicenseRequired && license) {
117129
if (!hasMinimumLicenseRequired(license.toLocaleLowerCase() as LicenseType)) {
118130
return false;

src/platform/packages/shared/kbn-esql-language/src/commands/definitions/utils/validation/function.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,14 @@ class FunctionValidator {
112112
return;
113113
}
114114

115+
// Return early so the source-incompatibility error takes priority over the generic
116+
// "not allowed here" check below — the location may technically match, but the function
117+
// is invalid regardless because the pipeline source is TS.
118+
if (isTimeseriesSourceCommand(this.ast) && this.definition.tsdbCompatible === false) {
119+
this.report(errors.tsdbIncompatibleFunction(this.fn));
120+
return;
121+
}
122+
115123
if (!this.allowedHere) {
116124
this.report(errors.functionNotAllowedHere(this.fn, this.location.displayName));
117125
}

src/platform/packages/shared/kbn-esql-language/src/commands/registry/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ export interface ICommandContext {
226226
isCursorInSubquery?: boolean;
227227
isFieldsBrowserEnabled?: boolean;
228228
unmappedFieldsStrategy?: UnmappedFieldsStrategy;
229+
isTimeseriesSource?: boolean;
229230
}
230231
/**
231232
* This is a list of locations within an ES|QL query.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
import { FunctionDefinitionTypes } from '../../../commands/definitions/types';
10+
import { Location } from '../../../commands/registry/types';
11+
import { setTestFunctions } from '../../../commands/definitions/utils/test_functions';
12+
import { setup } from './helpers';
13+
14+
const TS_INCOMPATIBLE_FN = {
15+
type: FunctionDefinitionTypes.AGG,
16+
name: 'TS_INCOMPATIBLE_AGG',
17+
description: 'An aggregation that is not supported in TS pipelines',
18+
signatures: [{ params: [], returnType: 'double' as const }],
19+
locationsAvailable: [Location.STATS],
20+
tsdbCompatible: false,
21+
};
22+
23+
const TS_COMPATIBLE_FN = {
24+
type: FunctionDefinitionTypes.AGG,
25+
name: 'TS_COMPATIBLE_AGG',
26+
description: 'An aggregation that is supported everywhere',
27+
signatures: [{ params: [], returnType: 'double' as const }],
28+
locationsAvailable: [Location.STATS],
29+
};
30+
31+
describe('tsdbCompatible filtering', () => {
32+
beforeEach(() => setTestFunctions([TS_INCOMPATIBLE_FN, TS_COMPATIBLE_FN]));
33+
afterEach(() => setTestFunctions([]));
34+
35+
it('excludes tsdbCompatible:false functions from suggestions in a TS pipeline', async () => {
36+
const { suggest } = await setup();
37+
const suggestions = (await suggest('TS index | STATS /')).map((s) => s.label);
38+
39+
expect(suggestions).not.toContain('TS_INCOMPATIBLE_AGG');
40+
expect(suggestions).toContain('TS_COMPATIBLE_AGG');
41+
});
42+
43+
it('includes tsdbCompatible:false functions in suggestions in a FROM pipeline', async () => {
44+
const { suggest } = await setup();
45+
const suggestions = (await suggest('FROM index | STATS /')).map((s) => s.label);
46+
47+
expect(suggestions).toContain('TS_INCOMPATIBLE_AGG');
48+
expect(suggestions).toContain('TS_COMPATIBLE_AGG');
49+
});
50+
});

src/platform/packages/shared/kbn-esql-language/src/language/autocomplete/autocomplete.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ async function getSuggestionsWithinCommandExpression(
326326
isCursorInSubquery: astContext.isCursorInSubquery,
327327
isFieldsBrowserEnabled: canSuggestResourceBrowser && !isInsideSubquery,
328328
unmappedFieldsStrategy,
329+
isTimeseriesSource: isTimeseriesSourceCommand(commands.filter((cmd) => cmd.type === 'command')),
329330
};
330331

331332
// Wrap getColumnsByType so the fields browser option is injected from context;

0 commit comments

Comments
 (0)