Skip to content
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

module.exports = {
preset: '@kbn/test/jest_integration_node',
rootDir: '../../../../..',
roots: ['<rootDir>/src/platform/packages/shared/kbn-esql-language'],
};
3 changes: 3 additions & 0 deletions src/platform/packages/shared/kbn-esql-language/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ dependsOn:
- '@kbn/core-pricing-common'
- '@kbn/licensing-types'
- '@kbn/field-types'
- '@kbn/test-es-server'
- '@kbn/tooling-log'
- '@kbn/esql-scripts'
tags:
- shared-common
Expand All @@ -36,6 +38,7 @@ fileGroups:
src:
- src/**/*
- '**/*.ts'
- jest.integration.config.js
- '!target/**/*'
jest-config:
- jest.config.js
Expand Down
Comment thread
stratoula marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -7,52 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { times } from 'lodash';
import { setup } from './helpers';
import { runColumnExistenceValidationSuite } from './column_existence_suite';

describe('column existence checks', () => {
it('looks behind current command', async () => {
const { expectErrors } = await setup();
await expectErrors('FROM index | DROP keywordField | KEEP keywordField', [
'Unknown column "keywordField"',
]);
});

it('treats FORK branches separately', async () => {
const { expectErrors } = await setup();
await expectErrors('FROM index | FORK (DROP keywordField) (KEEP keywordField)', []);
});

it('makes STATS generated columns available after inline WHERE filters', async () => {
const { expectErrors } = await setup();
await expectErrors(
'FROM index | STATS COUNT() WHERE integerField > 0 | EVAL result = `COUNT() WHERE integerField > 0` + 1',
[]
);
});

it('returns a warning instead of an error when unmapped_fields is LOAD or NULLIFY', async () => {
const { expectErrors } = await setup();
await expectErrors(
'SET unmapped_fields = "LOAD"; FROM index | WHERE unmapped == ""',
[],
[
`"unmapped" column isn't mapped in any searched indices.\nIf you are not intentionally referencing an unmapped field,\ncheck that the field exists or that it is spelled correctly in your query.`,
]
);
});

it('returns one warning for each instance of the same unmapped column', async () => {
const { expectErrors } = await setup();
await expectErrors(
'SET unmapped_fields = "LOAD"; FROM index | WHERE unmapped == "" | KEEP unmapped',
[],

times(
2,
() =>
`"unmapped" column isn't mapped in any searched indices.\nIf you are not intentionally referencing an unmapped field,\ncheck that the field exists or that it is spelled correctly in your query.`
)
);
});
});
runColumnExistenceValidationSuite(setup);
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { times } from 'lodash';
import type { Setup } from './helpers';

export const runColumnExistenceValidationSuite = (setup: Setup) => {
describe('column existence checks', () => {
it('looks behind current command', async () => {
const { expectErrors } = await setup();
await expectErrors('FROM index | DROP keywordField | KEEP keywordField', [
'Unknown column "keywordField"',
]);
});

it('treats FORK branches separately', async () => {
const { expectErrors } = await setup();
await expectErrors('FROM index | FORK (DROP keywordField) (KEEP keywordField)', []);
});

it('makes STATS generated columns available after inline WHERE filters', async () => {
const { expectErrors } = await setup();
await expectErrors(
'FROM index | STATS COUNT() WHERE integerField > 0 | EVAL result = `COUNT() WHERE integerField > 0` + 1',
[]
);
});

it('returns a warning instead of an error when unmapped_fields is LOAD or NULLIFY', async () => {
const { expectErrors } = await setup();
await expectErrors(
'SET unmapped_fields = "LOAD"; FROM index | WHERE unmapped == ""',
[],
[
`"unmapped" column isn't mapped in any searched indices.\nIf you are not intentionally referencing an unmapped field,\ncheck that the field exists or that it is spelled correctly in your query.`,
]
);
});

it('returns one warning for each instance of the same unmapped column', async () => {
const { expectErrors } = await setup();
await expectErrors(
'SET unmapped_fields = "LOAD"; FROM index | WHERE unmapped == "" | KEEP unmapped',
[],

times(
2,
() =>
`"unmapped" column isn't mapped in any searched indices.\nIf you are not intentionally referencing an unmapped field,\ncheck that the field exists or that it is spelled correctly in your query.`
)
);
});
});
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,217 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import {
type FunctionParameterType,
FunctionDefinitionTypes,
} from '../../../commands/definitions/types';
import { getNoValidCallSignatureError } from '../../../commands/definitions/utils/validation/utils';
import { Location } from '../../../commands/registry/types';
import { setTestFunctions } from '../../../commands/definitions/utils/test_functions';
import { setup } from './helpers';
import { runFieldsAndVariablesValidationSuite } from './fields_and_variables_suite';

describe('column escaping', () => {
it('recognizes escaped fields', async () => {
const { expectErrors } = await setup();
// command level
await expectErrors(
'FROM index | KEEP `kubernetes`.`something`.`something` | EVAL `kubernetes.something.something` + 12',
[]
);
// function argument
await expectErrors('FROM index | EVAL ABS(`kubernetes`.`something`.`something`)', []);
});

it('recognizes field names with spaces and comments', async () => {
const { expectErrors } = await setup();
// command level
await expectErrors('FROM index | KEEP kubernetes . something . /* gotcha! */ something', []);
// function argument
await expectErrors(
'FROM index | EVAL ABS(kubernetes . something . /* gotcha! */ something)',
[]
);
});

it('recognizes escaped user-defined columns', async () => {
const { expectErrors } = await setup();
// command level
await expectErrors('ROW `var$iable` = 1 | EVAL `var$iable`', []);

// command level, different escaping in declaration
await expectErrors(
'ROW variable.`wi#th`.separator = "lolz" | EVAL `variable`.`wi#th`.`separator`',
[]
);

// function arguments
await expectErrors(
'ROW `var$iable` = 1, variable.`wi#th`.separator = "lolz" | EVAL ABS(`var$iable`), TRIM(variable.`wi#th`.`separator`)',
[]
);

// expression user-defined column
await expectErrors('FROM index | EVAL doubleField + 20 | EVAL `doubleField + 20`', []);
await expectErrors('ROW 21 + 20 | STATS AVG(`21 + 20`)', []);
});

it('recognizes user-defined columns with spaces and comments', async () => {
const { expectErrors } = await setup();
// command level
await expectErrors(
'ROW variable.`wi#th`.separator = "lolz" | RENAME variable . /* lolz */ `wi#th` . separator AS foo',
[]
);
// function argument
await expectErrors(
'ROW variable.`wi#th`.separator = "lolz" | EVAL TRIM(variable . /* lolz */ `wi#th` . separator)',
[]
);
});

describe('as part of various commands', () => {
const cases = [
{ name: 'ROW', command: 'ROW `var$iable` = 1, variable.`wi#th`.separator = "lolz"' },
{
name: 'DISSECT',
command: 'ROW `funky`.`stri#$ng` = "lolz" | DISSECT `funky`.`stri#$ng` "%{WORD:firstWord}"',
},
{ name: 'DROP', command: 'FROM index | DROP kubernetes.`something`.`something`' },
{
name: 'ENRICH',
command:
'FROM index | ENRICH policy WITH `new`.name1 = `otherField`, `new.name2` = `yetAnotherField`',
},
{ name: 'EVAL', command: 'FROM index | EVAL kubernetes.`something`.`something` + 12' },
{
name: 'GROK',
command: 'ROW `funky`.`stri#$ng` = "lolz" | GROK `funky`.`stri#$ng` "%{WORD:firstWord}"',
},
{ name: 'KEEP', command: 'FROM index | KEEP kubernetes.`something`.`something`' },
{
name: 'RENAME',
command: 'FROM index | RENAME kubernetes.`something`.`something` as foobar',
},
{ name: 'SORT', command: 'FROM index | SORT kubernetes.`something`.`something` DESC' },
{
name: 'STATS ... BY',
command:
'FROM index | STATS AVG(kubernetes.`something`.`something`) BY `kubernetes`.`something`.`something`',
},
{ name: 'WHERE', command: 'FROM index | WHERE kubernetes.`something`.`something` == 12' },
];

it.each(cases)('$name accepts escaped fields', async ({ command }) => {
const { expectErrors } = await setup();
await expectErrors(command, []);
});
});
});

describe('user-defined column support', () => {
describe('user-defined column data type detection', () => {
beforeAll(() => {
setTestFunctions([
// this test function is just used to test the type of the user-defined column
{
type: FunctionDefinitionTypes.SCALAR,
description: 'Test function',
locationsAvailable: [Location.EVAL],
name: 'test',
signatures: [
{ params: [{ name: 'arg', type: 'cartesian_point' }], returnType: 'cartesian_point' },
],
},
// this test function is used to check that the correct return type is used
// when determining user-defined column types
{
type: FunctionDefinitionTypes.SCALAR,
description: 'Test function',
locationsAvailable: [Location.EVAL],
name: 'return_value',
signatures: [
{ params: [{ name: 'arg', type: 'text' }], returnType: 'text' },
{ params: [{ name: 'arg', type: 'double' }], returnType: 'double' },
{
params: [
{ name: 'arg', type: 'double' },
{ name: 'arg', type: 'text' },
],
returnType: 'long',
},
],
},
]);
});

afterAll(() => {
setTestFunctions([]);
});

const expectType = (type: FunctionParameterType) =>
getNoValidCallSignatureError('test', [type]);

test('literals', async () => {
const { expectErrors } = await setup();
// literal assignment
await expectErrors('FROM index | EVAL var = 1, TEST(var)', [expectType('integer')]);
// literal expression
await expectErrors('FROM index | EVAL 1, TEST(`1`)', [expectType('integer')]);
});

test('fields', async () => {
const { expectErrors } = await setup();
// field assignment
await expectErrors('FROM index | EVAL var = textField, TEST(var)', [
getNoValidCallSignatureError('test', ['text']),
]);
});

test('user-defined columns', async () => {
const { expectErrors } = await setup();
await expectErrors('FROM index | EVAL var = textField, col2 = var, TEST(col2)', [
getNoValidCallSignatureError('test', ['text']),
]);
});

test('inline casting', async () => {
const { expectErrors } = await setup();
// inline cast assignment
await expectErrors('FROM index | EVAL var = doubleField::long, TEST(var)', [
expectType('long'),
]);
// inline cast expression
await expectErrors('FROM index | EVAL doubleField::long, TEST(`doubleField::long`)', [
expectType('long'),
]);
});

test('function results', async () => {
const { expectErrors } = await setup();
// function assignment
await expectErrors('FROM index | EVAL var = RETURN_VALUE(doubleField), TEST(var)', [
expectType('double'),
]);
await expectErrors('FROM index | EVAL var = RETURN_VALUE(textField), TEST(var)', [
expectType('text'),
]);
await expectErrors(
'FROM index | EVAL var = RETURN_VALUE(doubleField, textField), TEST(var)',
[expectType('long')]
);
// function expression
await expectErrors(
'FROM index | EVAL RETURN_VALUE(doubleField), TEST(`RETURN_VALUE(doubleField)`)',
[expectType('double')]
);
await expectErrors(
'FROM index | EVAL RETURN_VALUE(textField), TEST(`RETURN_VALUE(textField)`)',
[expectType('text')]
);
await expectErrors(
'FROM index | EVAL RETURN_VALUE(doubleField, textField), TEST(`RETURN_VALUE(doubleField, textField)`)',
[expectType('long')]
);
});
});
});
runFieldsAndVariablesValidationSuite(setup);
Loading
Loading