mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[KQL] Add support for toKqlExpression (#161601)
## Summary Resolves https://github.com/elastic/kibana/issues/77971. Adds a `toKqlExpression` method to the `@kbn/es-query` that allows generating a KQL expression from an AST node. Example: ```ts const node = fromKueryExpression('extension: "jpg"'); const kql = toKqlExpression(node); // 'extension: "jpg"' ``` Note that the generated KQL expression may not exactly match the original text (whitespace is not preserved, parentheses may be added, etc.). ### Checklist Delete any items that are not applicable to this PR. - [ ] [Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html) was added for features that require explanation or tutorials - [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
This commit is contained in:
parent
e9a0ad188b
commit
12e748486c
28 changed files with 750 additions and 141 deletions
|
@ -40,13 +40,7 @@ export type {
|
|||
CombinedFilter,
|
||||
} from './src/filters';
|
||||
|
||||
export type {
|
||||
DslQuery,
|
||||
FunctionTypeBuildNode,
|
||||
KueryNode,
|
||||
KueryParseOptions,
|
||||
KueryQueryOptions,
|
||||
} from './src/kuery';
|
||||
export type { DslQuery, KueryNode, KueryParseOptions, KueryQueryOptions } from './src/kuery';
|
||||
|
||||
export {
|
||||
buildEsQuery,
|
||||
|
@ -117,6 +111,7 @@ export {
|
|||
export {
|
||||
KQLSyntaxError,
|
||||
fromKueryExpression,
|
||||
toKqlExpression,
|
||||
nodeBuilder,
|
||||
nodeTypes,
|
||||
toElasticsearchQuery,
|
||||
|
|
|
@ -6,7 +6,12 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { fromKueryExpression, fromLiteralExpression, toElasticsearchQuery } from './ast';
|
||||
import {
|
||||
fromKueryExpression,
|
||||
fromLiteralExpression,
|
||||
toElasticsearchQuery,
|
||||
toKqlExpression,
|
||||
} from './ast';
|
||||
import { nodeTypes } from '../node_types';
|
||||
import { DataViewBase } from '../../..';
|
||||
import { KueryNode } from '../types';
|
||||
|
@ -387,4 +392,44 @@ describe('kuery AST API', () => {
|
|||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test('function node', () => {
|
||||
const node = nodeTypes.function.buildNode('exists', 'response');
|
||||
const result = toKqlExpression(node);
|
||||
expect(result).toEqual('response: *');
|
||||
});
|
||||
|
||||
test('literal node', () => {
|
||||
const node = nodeTypes.literal.buildNode('foo');
|
||||
const result = toKqlExpression(node);
|
||||
expect(result).toEqual('foo');
|
||||
});
|
||||
|
||||
test('wildcard node', () => {
|
||||
const node = nodeTypes.wildcard.buildNode('foo*bar');
|
||||
const result = toKqlExpression(node);
|
||||
expect(result).toEqual('foo*bar');
|
||||
});
|
||||
|
||||
test('should throw an error with invalid node type', () => {
|
||||
const noTypeNode = nodeTypes.function.buildNode('exists', 'foo');
|
||||
|
||||
// @ts-expect-error
|
||||
delete noTypeNode.type;
|
||||
expect(() => toKqlExpression(noTypeNode)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"Unknown KQL node type: \\"undefined\\""`
|
||||
);
|
||||
});
|
||||
|
||||
test('fromKueryExpression toKqlExpression', () => {
|
||||
const node = fromKueryExpression(
|
||||
'field: (value AND value2 OR "value3") OR nested: { field2: value4 }'
|
||||
);
|
||||
const result = toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"(((field: value AND field: value2) OR field: \\"value3\\") OR nested: { field2: value4 })"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -56,6 +56,18 @@ export const fromKueryExpression = (
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Given a KQL AST node, generate the corresponding KQL expression.
|
||||
* @public
|
||||
* @param node
|
||||
*/
|
||||
export function toKqlExpression(node: KueryNode): string {
|
||||
if (nodeTypes.function.isNode(node)) return nodeTypes.function.toKqlExpression(node);
|
||||
if (nodeTypes.literal.isNode(node)) return nodeTypes.literal.toKqlExpression(node);
|
||||
if (nodeTypes.wildcard.isNode(node)) return nodeTypes.wildcard.toKqlExpression(node);
|
||||
throw new Error(`Unknown KQL node type: "${node.type}"`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @params {String} indexPattern
|
||||
* @params {Object} config - contains the dateFormatTZ
|
||||
|
|
|
@ -11,6 +11,7 @@ import { fields } from '../../filters/stubs';
|
|||
import * as ast from '../ast';
|
||||
import * as and from './and';
|
||||
import { DataViewBase } from '../../es_query';
|
||||
import { KqlAndFunctionNode } from './and';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -42,15 +43,18 @@ describe('kuery functions', () => {
|
|||
|
||||
describe('toElasticsearchQuery', () => {
|
||||
test("should wrap subqueries in an ES bool query's filter clause", () => {
|
||||
const node = nodeTypes.function.buildNode('and', [childNode1, childNode2]);
|
||||
const node = nodeTypes.function.buildNode('and', [
|
||||
childNode1,
|
||||
childNode2,
|
||||
]) as KqlAndFunctionNode;
|
||||
const result = and.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toHaveProperty('bool');
|
||||
expect(Object.keys(result).length).toBe(1);
|
||||
expect(result.bool).toHaveProperty('filter');
|
||||
expect(Object.keys(result.bool).length).toBe(1);
|
||||
expect(Object.keys(result.bool!).length).toBe(1);
|
||||
|
||||
expect(result.bool.filter).toEqual(
|
||||
expect(result.bool!.filter).toEqual(
|
||||
[childNode1, childNode2].map((childNode) =>
|
||||
ast.toElasticsearchQuery(childNode, indexPattern)
|
||||
)
|
||||
|
@ -58,7 +62,10 @@ describe('kuery functions', () => {
|
|||
});
|
||||
|
||||
test("should wrap subqueries in an ES bool query's must clause for scoring if enabled", () => {
|
||||
const node = nodeTypes.function.buildNode('and', [childNode1, childNode2]);
|
||||
const node = nodeTypes.function.buildNode('and', [
|
||||
childNode1,
|
||||
childNode2,
|
||||
]) as KqlAndFunctionNode;
|
||||
const result = and.toElasticsearchQuery(node, indexPattern, {
|
||||
filtersInMustClause: true,
|
||||
});
|
||||
|
@ -66,14 +73,31 @@ describe('kuery functions', () => {
|
|||
expect(result).toHaveProperty('bool');
|
||||
expect(Object.keys(result).length).toBe(1);
|
||||
expect(result.bool).toHaveProperty('must');
|
||||
expect(Object.keys(result.bool).length).toBe(1);
|
||||
expect(Object.keys(result.bool!).length).toBe(1);
|
||||
|
||||
expect(result.bool.must).toEqual(
|
||||
expect(result.bool!.must).toEqual(
|
||||
[childNode1, childNode2].map((childNode) =>
|
||||
ast.toElasticsearchQuery(childNode, indexPattern)
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test('with one sub-expression', () => {
|
||||
const node = nodeTypes.function.buildNode('and', [childNode1]) as KqlAndFunctionNode;
|
||||
const result = and.toKqlExpression(node);
|
||||
expect(result).toBe('(machine.os: osx)');
|
||||
});
|
||||
|
||||
test('with two sub-expressions', () => {
|
||||
const node = nodeTypes.function.buildNode('and', [
|
||||
childNode1,
|
||||
childNode2,
|
||||
]) as KqlAndFunctionNode;
|
||||
const result = and.toKqlExpression(node);
|
||||
expect(result).toBe('(machine.os: osx AND extension: jpg)');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,10 +6,23 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import * as ast from '../ast';
|
||||
import type { DataViewBase, KueryNode, KueryQueryOptions } from '../../..';
|
||||
import type { KqlFunctionNode } from '../node_types';
|
||||
import type { KqlContext } from '../types';
|
||||
|
||||
export const KQL_FUNCTION_AND = 'and';
|
||||
|
||||
export interface KqlAndFunctionNode extends KqlFunctionNode {
|
||||
function: typeof KQL_FUNCTION_AND;
|
||||
arguments: KqlFunctionNode[];
|
||||
}
|
||||
|
||||
export function isNode(node: KqlFunctionNode): node is KqlAndFunctionNode {
|
||||
return node.function === KQL_FUNCTION_AND;
|
||||
}
|
||||
|
||||
export function buildNodeParams(children: KueryNode[]) {
|
||||
return {
|
||||
arguments: children,
|
||||
|
@ -17,11 +30,11 @@ export function buildNodeParams(children: KueryNode[]) {
|
|||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
node: KqlAndFunctionNode,
|
||||
indexPattern?: DataViewBase,
|
||||
config: KueryQueryOptions = {},
|
||||
context: KqlContext = {}
|
||||
) {
|
||||
): QueryDslQueryContainer {
|
||||
const { filtersInMustClause } = config;
|
||||
const children = node.arguments || [];
|
||||
const key = filtersInMustClause ? 'must' : 'filter';
|
||||
|
@ -34,3 +47,7 @@ export function toElasticsearchQuery(
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlAndFunctionNode): string {
|
||||
return `(${node.arguments.map(ast.toKqlExpression).join(' AND ')})`;
|
||||
}
|
||||
|
|
|
@ -10,12 +10,11 @@ import { nodeTypes } from '../node_types';
|
|||
import { fields } from '../../filters/stubs';
|
||||
import { DataViewBase } from '../../..';
|
||||
import { KQL_NODE_TYPE_LITERAL } from '../node_types/literal';
|
||||
import * as exists from './exists';
|
||||
import type { KqlExistsFunctionNode } from './exists';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
// @ts-ignore
|
||||
import * as exists from './exists';
|
||||
|
||||
describe('kuery functions', () => {
|
||||
describe('exists', () => {
|
||||
let indexPattern: DataViewBase;
|
||||
|
@ -50,7 +49,10 @@ describe('kuery functions', () => {
|
|||
const expected = {
|
||||
exists: { field: 'response' },
|
||||
};
|
||||
const existsNode = nodeTypes.function.buildNode('exists', 'response');
|
||||
const existsNode = nodeTypes.function.buildNode(
|
||||
'exists',
|
||||
'response'
|
||||
) as KqlExistsFunctionNode;
|
||||
const result = exists.toElasticsearchQuery(existsNode, indexPattern);
|
||||
|
||||
expect(expected).toEqual(result);
|
||||
|
@ -60,14 +62,20 @@ describe('kuery functions', () => {
|
|||
const expected = {
|
||||
exists: { field: 'response' },
|
||||
};
|
||||
const existsNode = nodeTypes.function.buildNode('exists', 'response');
|
||||
const existsNode = nodeTypes.function.buildNode(
|
||||
'exists',
|
||||
'response'
|
||||
) as KqlExistsFunctionNode;
|
||||
const result = exists.toElasticsearchQuery(existsNode);
|
||||
|
||||
expect(expected).toEqual(result);
|
||||
});
|
||||
|
||||
test('should throw an error for scripted fields', () => {
|
||||
const existsNode = nodeTypes.function.buildNode('exists', 'script string');
|
||||
const existsNode = nodeTypes.function.buildNode(
|
||||
'exists',
|
||||
'script string'
|
||||
) as KqlExistsFunctionNode;
|
||||
expect(() => exists.toElasticsearchQuery(existsNode, indexPattern)).toThrowError(
|
||||
/Exists query does not support scripted fields/
|
||||
);
|
||||
|
@ -77,7 +85,10 @@ describe('kuery functions', () => {
|
|||
const expected = {
|
||||
exists: { field: 'nestedField.response' },
|
||||
};
|
||||
const existsNode = nodeTypes.function.buildNode('exists', 'response');
|
||||
const existsNode = nodeTypes.function.buildNode(
|
||||
'exists',
|
||||
'response'
|
||||
) as KqlExistsFunctionNode;
|
||||
const result = exists.toElasticsearchQuery(
|
||||
existsNode,
|
||||
indexPattern,
|
||||
|
@ -88,5 +99,16 @@ describe('kuery functions', () => {
|
|||
expect(expected).toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test('should return a KQL expression', () => {
|
||||
const existsNode = nodeTypes.function.buildNode(
|
||||
'exists',
|
||||
'response'
|
||||
) as KqlExistsFunctionNode;
|
||||
const result = exists.toKqlExpression(existsNode);
|
||||
expect(result).toBe('response: *');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,23 +6,39 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { DataViewFieldBase, DataViewBase, KueryNode, KueryQueryOptions } from '../../..';
|
||||
import * as literal from '../node_types/literal';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { DataViewFieldBase, DataViewBase, KueryQueryOptions } from '../../..';
|
||||
import type { KqlFunctionNode, KqlLiteralNode } from '../node_types';
|
||||
import type { KqlContext } from '../types';
|
||||
import {
|
||||
buildNode as buildLiteralNode,
|
||||
toElasticsearchQuery as literalToElasticsearchQuery,
|
||||
toKqlExpression as literalToKqlExpression,
|
||||
} from '../node_types/literal';
|
||||
|
||||
export const KQL_FUNCTION_EXISTS = 'exists';
|
||||
|
||||
export interface KqlExistsFunctionNode extends KqlFunctionNode {
|
||||
function: typeof KQL_FUNCTION_EXISTS;
|
||||
arguments: [KqlLiteralNode];
|
||||
}
|
||||
|
||||
export function isNode(node: KqlFunctionNode): node is KqlExistsFunctionNode {
|
||||
return node.function === KQL_FUNCTION_EXISTS;
|
||||
}
|
||||
|
||||
export function buildNodeParams(fieldName: string) {
|
||||
return {
|
||||
arguments: [literal.buildNode(fieldName)],
|
||||
arguments: [buildLiteralNode(fieldName)],
|
||||
};
|
||||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
node: KqlExistsFunctionNode,
|
||||
indexPattern?: DataViewBase,
|
||||
config: KueryQueryOptions = {},
|
||||
context: KqlContext = {}
|
||||
): estypes.QueryDslQueryContainer {
|
||||
): QueryDslQueryContainer {
|
||||
const {
|
||||
arguments: [fieldNameArg],
|
||||
} = node;
|
||||
|
@ -30,7 +46,7 @@ export function toElasticsearchQuery(
|
|||
...fieldNameArg,
|
||||
value: context?.nested ? `${context.nested.path}.${fieldNameArg.value}` : fieldNameArg.value,
|
||||
};
|
||||
const fieldName = literal.toElasticsearchQuery(fullFieldNameArg) as string;
|
||||
const fieldName = literalToElasticsearchQuery(fullFieldNameArg) as string;
|
||||
const field = indexPattern?.fields?.find((fld: DataViewFieldBase) => fld.name === fieldName);
|
||||
|
||||
if (field?.scripted) {
|
||||
|
@ -40,3 +56,8 @@ export function toElasticsearchQuery(
|
|||
exists: { field: fieldName },
|
||||
};
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlExistsFunctionNode): string {
|
||||
const [field] = node.arguments;
|
||||
return `${literalToKqlExpression(field)}: *`;
|
||||
}
|
||||
|
|
|
@ -14,6 +14,14 @@ import * as range from './range';
|
|||
import * as exists from './exists';
|
||||
import * as nested from './nested';
|
||||
|
||||
export { KQL_FUNCTION_AND } from './and';
|
||||
export { KQL_FUNCTION_EXISTS } from './exists';
|
||||
export { KQL_FUNCTION_IS } from './is';
|
||||
export { KQL_FUNCTION_NESTED } from './nested';
|
||||
export { KQL_FUNCTION_NOT } from './not';
|
||||
export { KQL_FUNCTION_OR } from './or';
|
||||
export { KQL_FUNCTION_RANGE } from './range';
|
||||
|
||||
export const functions = {
|
||||
is,
|
||||
and,
|
||||
|
|
|
@ -14,6 +14,7 @@ import { DataViewBase } from '../../..';
|
|||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { KQL_NODE_TYPE_WILDCARD } from '../node_types/wildcard';
|
||||
import { KQL_NODE_TYPE_LITERAL } from '../node_types/literal';
|
||||
import { KqlIsFunctionNode } from './is';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -69,7 +70,7 @@ describe('kuery functions', () => {
|
|||
const expected = {
|
||||
match_all: {},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', '*', '*');
|
||||
const node = nodeTypes.function.buildNode('is', '*', '*') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -79,7 +80,7 @@ describe('kuery functions', () => {
|
|||
const expected = {
|
||||
match_all: {},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'n*', '*');
|
||||
const node = nodeTypes.function.buildNode('is', 'n*', '*') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, {
|
||||
...indexPattern,
|
||||
fields: indexPattern.fields.filter((field) => field.name.startsWith('n')),
|
||||
|
@ -92,7 +93,7 @@ describe('kuery functions', () => {
|
|||
const expected = {
|
||||
match_all: {},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', '*', '*');
|
||||
const node = nodeTypes.function.buildNode('is', '*', '*') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -106,7 +107,7 @@ describe('kuery functions', () => {
|
|||
lenient: true,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', null, 200);
|
||||
const node = nodeTypes.function.buildNode('is', null, 200) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -118,14 +119,14 @@ describe('kuery functions', () => {
|
|||
query: 'jpg*',
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', null, 'jpg*');
|
||||
const node = nodeTypes.function.buildNode('is', null, 'jpg*') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('should return an ES bool query with a sub-query for each field when fieldName is "*"', () => {
|
||||
const node = nodeTypes.function.buildNode('is', '*', 200);
|
||||
const node = nodeTypes.function.buildNode('is', '*', 200) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toHaveProperty('bool');
|
||||
|
@ -141,7 +142,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', '*');
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', '*') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -154,7 +155,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -167,7 +168,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -180,7 +181,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', '"jpg"');
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', '"jpg"') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -200,7 +201,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg*');
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg*') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -219,7 +220,11 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'machine.os.keyword', 'win*');
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'machine.os.keyword',
|
||||
'win*'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -238,14 +243,22 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'machine.os.keyword', 'win*');
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'machine.os.keyword',
|
||||
'win*'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern, { caseInsensitive: true });
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('should support scripted fields', () => {
|
||||
const node = nodeTypes.function.buildNode('is', 'script string', 'foo');
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'script string',
|
||||
'foo'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect((result.bool!.should as estypes.QueryDslQueryContainer[])[0]).toHaveProperty(
|
||||
|
@ -269,7 +282,11 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"');
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'@timestamp',
|
||||
'"2018-04-03T19:04:17"'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -293,7 +310,11 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', '@timestamp', '"2018-04-03T19:04:17"');
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'@timestamp',
|
||||
'"2018-04-03T19:04:17"'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern, config);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -306,7 +327,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg');
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(
|
||||
node,
|
||||
indexPattern,
|
||||
|
@ -324,7 +345,7 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', 'ext*', 'jpg');
|
||||
const node = nodeTypes.function.buildNode('is', 'ext*', 'jpg') as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -349,7 +370,11 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', '*doublyNested*', 'foo');
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'*doublyNested*',
|
||||
'foo'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -375,14 +400,22 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('is', '*doublyNested*', 'foo');
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'*doublyNested*',
|
||||
'foo'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern, { nestedIgnoreUnmapped: true });
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('should use a term query for keyword fields', () => {
|
||||
const node = nodeTypes.function.buildNode('is', 'machine.os.keyword', 'Win 7');
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'machine.os.keyword',
|
||||
'Win 7'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern);
|
||||
expect(result).toEqual({
|
||||
bool: {
|
||||
|
@ -399,7 +432,11 @@ describe('kuery functions', () => {
|
|||
});
|
||||
|
||||
test('should use a case-insensitive term query for keyword fields', () => {
|
||||
const node = nodeTypes.function.buildNode('is', 'machine.os.keyword', 'Win 7');
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'machine.os.keyword',
|
||||
'Win 7'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toElasticsearchQuery(node, indexPattern, { caseInsensitive: true });
|
||||
expect(result).toEqual({
|
||||
bool: {
|
||||
|
@ -415,5 +452,71 @@ describe('kuery functions', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test('match all fields and all values', () => {
|
||||
const node = nodeTypes.function.buildNode('is', '*', '*') as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"*: *"`);
|
||||
});
|
||||
|
||||
test('no field with literal value', () => {
|
||||
const node = nodeTypes.function.buildNode('is', null, 200) as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"200"`);
|
||||
});
|
||||
|
||||
test('no field with wildcard value', () => {
|
||||
const node = nodeTypes.function.buildNode('is', null, 'jpg*') as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"jpg*"`);
|
||||
});
|
||||
|
||||
test('match all fields with value', () => {
|
||||
const node = nodeTypes.function.buildNode('is', '*', 200) as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"*: 200"`);
|
||||
});
|
||||
|
||||
test('field with match all value"', () => {
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', '*') as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"extension: *"`);
|
||||
});
|
||||
|
||||
test('field with value', () => {
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg') as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"extension: jpg"`);
|
||||
});
|
||||
|
||||
test('field with phrase value', () => {
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', '"jpg"') as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"extension: \\"jpg\\""`);
|
||||
});
|
||||
|
||||
test('phrase field with phrase value', () => {
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'is',
|
||||
'"extension"',
|
||||
'"jpg"'
|
||||
) as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"\\"extension\\": \\"jpg\\""`);
|
||||
});
|
||||
|
||||
test('field with wildcard value', () => {
|
||||
const node = nodeTypes.function.buildNode('is', 'extension', 'jpg*') as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"extension: jpg*"`);
|
||||
});
|
||||
|
||||
test('wildcard field with value', () => {
|
||||
const node = nodeTypes.function.buildNode('is', 'ext*', 'jpg') as KqlIsFunctionNode;
|
||||
const result = is.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"ext*: jpg"`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -7,18 +7,30 @@
|
|||
*/
|
||||
|
||||
import { isUndefined } from 'lodash';
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { getPhraseScript } from '../../filters';
|
||||
import { getFields } from './utils/get_fields';
|
||||
import { getTimeZoneFromSettings, getDataViewFieldSubtypeNested } from '../../utils';
|
||||
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
|
||||
import type { DataViewBase, KueryNode, DataViewFieldBase, KueryQueryOptions } from '../../..';
|
||||
import type { DataViewBase, DataViewFieldBase, KueryQueryOptions } from '../../..';
|
||||
import type { KqlFunctionNode, KqlLiteralNode, KqlWildcardNode } from '../node_types';
|
||||
import type { KqlContext } from '../types';
|
||||
|
||||
import * as ast from '../ast';
|
||||
import * as literal from '../node_types/literal';
|
||||
import * as wildcard from '../node_types/wildcard';
|
||||
|
||||
export const KQL_FUNCTION_IS = 'is';
|
||||
|
||||
export interface KqlIsFunctionNode extends KqlFunctionNode {
|
||||
function: typeof KQL_FUNCTION_IS;
|
||||
arguments: [KqlLiteralNode | KqlWildcardNode, KqlLiteralNode | KqlWildcardNode];
|
||||
}
|
||||
|
||||
export function isNode(node: KqlFunctionNode): node is KqlIsFunctionNode {
|
||||
return node.function === KQL_FUNCTION_IS;
|
||||
}
|
||||
|
||||
export function buildNodeParams(fieldName: string, value: any) {
|
||||
if (isUndefined(fieldName)) {
|
||||
throw new Error('fieldName is a required argument');
|
||||
|
@ -38,11 +50,11 @@ export function buildNodeParams(fieldName: string, value: any) {
|
|||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
node: KqlIsFunctionNode,
|
||||
indexPattern?: DataViewBase,
|
||||
config: KueryQueryOptions = {},
|
||||
context: KqlContext = {}
|
||||
): estypes.QueryDslQueryContainer {
|
||||
): QueryDslQueryContainer {
|
||||
const {
|
||||
arguments: [fieldNameArg, valueArg],
|
||||
} = node;
|
||||
|
@ -216,3 +228,9 @@ export function toElasticsearchQuery(
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlIsFunctionNode): string {
|
||||
const [field, value] = node.arguments;
|
||||
if (field.value === null) return `${ast.toKqlExpression(value)}`;
|
||||
return `${ast.toKqlExpression(field)}: ${ast.toKqlExpression(value)}`;
|
||||
}
|
||||
|
|
|
@ -9,10 +9,9 @@
|
|||
import { nodeTypes } from '../node_types';
|
||||
import { fields } from '../../filters/stubs';
|
||||
import { DataViewBase } from '../../..';
|
||||
|
||||
import * as ast from '../ast';
|
||||
|
||||
import * as nested from './nested';
|
||||
import type { KqlNestedFunctionNode } from './nested';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -43,7 +42,11 @@ describe('kuery functions', () => {
|
|||
|
||||
describe('toElasticsearchQuery', () => {
|
||||
test('should wrap subqueries in an ES nested query', () => {
|
||||
const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'nested',
|
||||
'nestedField',
|
||||
childNode
|
||||
) as KqlNestedFunctionNode;
|
||||
const result = nested.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toHaveProperty('nested');
|
||||
|
@ -54,7 +57,11 @@ describe('kuery functions', () => {
|
|||
});
|
||||
|
||||
test('should pass the nested path to subqueries so the full field name can be used', () => {
|
||||
const node = nodeTypes.function.buildNode('nested', 'nestedField', childNode);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'nested',
|
||||
'nestedField',
|
||||
childNode
|
||||
) as KqlNestedFunctionNode;
|
||||
const result = nested.toElasticsearchQuery(node, indexPattern);
|
||||
const expectedSubQuery = ast.toElasticsearchQuery(
|
||||
nodeTypes.function.buildNode('is', 'nestedField.child', 'foo')
|
||||
|
@ -63,5 +70,41 @@ describe('kuery functions', () => {
|
|||
expect(result.nested!.query).toEqual(expectedSubQuery);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test('single nested query', () => {
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'nested',
|
||||
'nestedField',
|
||||
childNode
|
||||
) as KqlNestedFunctionNode;
|
||||
const result = nested.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"nestedField: { child: foo }"`);
|
||||
});
|
||||
|
||||
test('multiple nested queries', () => {
|
||||
const andNode = nodeTypes.function.buildNode('and', [childNode, childNode]);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'nested',
|
||||
'nestedField',
|
||||
andNode
|
||||
) as KqlNestedFunctionNode;
|
||||
const result = nested.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"nestedField: { (child: foo AND child: foo) }"`);
|
||||
});
|
||||
|
||||
test('doubly nested query', () => {
|
||||
const subNode = nodeTypes.function.buildNode('nested', 'anotherNestedField', childNode);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'nested',
|
||||
'nestedField',
|
||||
subNode
|
||||
) as KqlNestedFunctionNode;
|
||||
const result = nested.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(
|
||||
`"nestedField: { anotherNestedField: { child: foo } }"`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,12 +6,24 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import * as ast from '../ast';
|
||||
import * as literal from '../node_types/literal';
|
||||
import type { DataViewBase, KueryNode, KueryQueryOptions } from '../../..';
|
||||
import type { DataViewBase, KueryQueryOptions } from '../../..';
|
||||
import type { KqlFunctionNode, KqlLiteralNode } from '../node_types';
|
||||
import type { KqlContext } from '../types';
|
||||
|
||||
export const KQL_FUNCTION_NESTED = 'nested';
|
||||
|
||||
export interface KqlNestedFunctionNode extends KqlFunctionNode {
|
||||
function: typeof KQL_FUNCTION_NESTED;
|
||||
arguments: [KqlLiteralNode, KqlFunctionNode];
|
||||
}
|
||||
|
||||
export function isNode(node: KqlFunctionNode): node is KqlNestedFunctionNode {
|
||||
return node.function === KQL_FUNCTION_NESTED;
|
||||
}
|
||||
|
||||
export function buildNodeParams(path: any, child: any) {
|
||||
const pathNode =
|
||||
typeof path === 'string' ? ast.fromLiteralExpression(path) : literal.buildNode(path);
|
||||
|
@ -21,11 +33,11 @@ export function buildNodeParams(path: any, child: any) {
|
|||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
node: KqlNestedFunctionNode,
|
||||
indexPattern?: DataViewBase,
|
||||
config: KueryQueryOptions = {},
|
||||
context: KqlContext = {}
|
||||
): estypes.QueryDslQueryContainer {
|
||||
): QueryDslQueryContainer {
|
||||
const [path, child] = node.arguments;
|
||||
const stringPath = ast.toElasticsearchQuery(path) as unknown as string;
|
||||
const fullPath = context?.nested?.path ? `${context.nested.path}.${stringPath}` : stringPath;
|
||||
|
@ -36,7 +48,7 @@ export function toElasticsearchQuery(
|
|||
query: ast.toElasticsearchQuery(child, indexPattern, config, {
|
||||
...context,
|
||||
nested: { path: fullPath },
|
||||
}) as estypes.QueryDslQueryContainer,
|
||||
}),
|
||||
score_mode: 'none',
|
||||
...(typeof config.nestedIgnoreUnmapped === 'boolean' && {
|
||||
ignore_unmapped: config.nestedIgnoreUnmapped,
|
||||
|
@ -44,3 +56,8 @@ export function toElasticsearchQuery(
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlNestedFunctionNode): string {
|
||||
const [path, child] = node.arguments;
|
||||
return `${literal.toKqlExpression(path)}: { ${ast.toKqlExpression(child)} }`;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { DataViewBase } from '../../..';
|
|||
|
||||
import * as ast from '../ast';
|
||||
import * as not from './not';
|
||||
import { KqlNotFunctionNode } from './not';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -40,7 +41,7 @@ describe('kuery functions', () => {
|
|||
|
||||
describe('toElasticsearchQuery', () => {
|
||||
test("should wrap a subquery in an ES bool query's must_not clause", () => {
|
||||
const node = nodeTypes.function.buildNode('not', childNode);
|
||||
const node = nodeTypes.function.buildNode('not', childNode) as KqlNotFunctionNode;
|
||||
const result = not.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toHaveProperty('bool');
|
||||
|
@ -52,5 +53,13 @@ describe('kuery functions', () => {
|
|||
expect(result.bool!.must_not).toEqual(ast.toElasticsearchQuery(childNode, indexPattern));
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test('with one sub-expression', () => {
|
||||
const node = nodeTypes.function.buildNode('not', childNode) as KqlNotFunctionNode;
|
||||
const result = not.toKqlExpression(node);
|
||||
expect(result).toBe('NOT extension: jpg');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,11 +6,23 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import * as ast from '../ast';
|
||||
import type { DataViewBase, KueryNode, KueryQueryOptions } from '../../..';
|
||||
import type { KqlFunctionNode } from '../node_types';
|
||||
import type { KqlContext } from '../types';
|
||||
|
||||
export const KQL_FUNCTION_NOT = 'not';
|
||||
|
||||
export interface KqlNotFunctionNode extends KqlFunctionNode {
|
||||
function: typeof KQL_FUNCTION_NOT;
|
||||
arguments: [KqlFunctionNode];
|
||||
}
|
||||
|
||||
export function isNode(node: KqlFunctionNode): node is KqlNotFunctionNode {
|
||||
return node.function === KQL_FUNCTION_NOT;
|
||||
}
|
||||
|
||||
export function buildNodeParams(child: KueryNode) {
|
||||
return {
|
||||
arguments: [child],
|
||||
|
@ -18,21 +30,21 @@ export function buildNodeParams(child: KueryNode) {
|
|||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
node: KqlNotFunctionNode,
|
||||
indexPattern?: DataViewBase,
|
||||
config: KueryQueryOptions = {},
|
||||
context: KqlContext = {}
|
||||
): estypes.QueryDslQueryContainer {
|
||||
): QueryDslQueryContainer {
|
||||
const [argument] = node.arguments;
|
||||
|
||||
return {
|
||||
bool: {
|
||||
must_not: ast.toElasticsearchQuery(
|
||||
argument,
|
||||
indexPattern,
|
||||
config,
|
||||
context
|
||||
) as estypes.QueryDslQueryContainer,
|
||||
must_not: ast.toElasticsearchQuery(argument, indexPattern, config, context),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlNotFunctionNode): string {
|
||||
const [child] = node.arguments;
|
||||
return `NOT ${ast.toKqlExpression(child)}`;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { DataViewBase } from '../../..';
|
|||
import * as ast from '../ast';
|
||||
|
||||
import * as or from './or';
|
||||
import { KqlOrFunctionNode } from './or';
|
||||
jest.mock('../grammar');
|
||||
|
||||
const childNode1 = nodeTypes.function.buildNode('is', 'machine.os', 'osx');
|
||||
|
@ -43,7 +44,10 @@ describe('kuery functions', () => {
|
|||
|
||||
describe('toElasticsearchQuery', () => {
|
||||
test("should wrap subqueries in an ES bool query's should clause", () => {
|
||||
const node = nodeTypes.function.buildNode('or', [childNode1, childNode2]);
|
||||
const node = nodeTypes.function.buildNode('or', [
|
||||
childNode1,
|
||||
childNode2,
|
||||
]) as KqlOrFunctionNode;
|
||||
const result = or.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toHaveProperty('bool');
|
||||
|
@ -57,11 +61,31 @@ describe('kuery functions', () => {
|
|||
});
|
||||
|
||||
test('should require one of the clauses to match', () => {
|
||||
const node = nodeTypes.function.buildNode('or', [childNode1, childNode2]);
|
||||
const node = nodeTypes.function.buildNode('or', [
|
||||
childNode1,
|
||||
childNode2,
|
||||
]) as KqlOrFunctionNode;
|
||||
const result = or.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result.bool).toHaveProperty('minimum_should_match', 1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test('with one sub-expression', () => {
|
||||
const node = nodeTypes.function.buildNode('or', [childNode1]) as KqlOrFunctionNode;
|
||||
const result = or.toKqlExpression(node);
|
||||
expect(result).toBe('(machine.os: osx)');
|
||||
});
|
||||
|
||||
test('with two sub-expressions', () => {
|
||||
const node = nodeTypes.function.buildNode('or', [
|
||||
childNode1,
|
||||
childNode2,
|
||||
]) as KqlOrFunctionNode;
|
||||
const result = or.toKqlExpression(node);
|
||||
expect(result).toBe('(machine.os: osx OR extension: jpg)');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,11 +6,23 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import * as ast from '../ast';
|
||||
import type { DataViewBase, KueryNode, KueryQueryOptions } from '../../..';
|
||||
import type { KqlFunctionNode } from '../node_types';
|
||||
import type { KqlContext } from '../types';
|
||||
|
||||
export const KQL_FUNCTION_OR = 'or';
|
||||
|
||||
export interface KqlOrFunctionNode extends KqlFunctionNode {
|
||||
function: typeof KQL_FUNCTION_OR;
|
||||
arguments: KqlFunctionNode[];
|
||||
}
|
||||
|
||||
export function isNode(node: KqlFunctionNode): node is KqlOrFunctionNode {
|
||||
return node.function === KQL_FUNCTION_OR;
|
||||
}
|
||||
|
||||
export function buildNodeParams(children: KueryNode[]) {
|
||||
return {
|
||||
arguments: children,
|
||||
|
@ -18,11 +30,11 @@ export function buildNodeParams(children: KueryNode[]) {
|
|||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
node: KqlOrFunctionNode,
|
||||
indexPattern?: DataViewBase,
|
||||
config: KueryQueryOptions = {},
|
||||
context: KqlContext = {}
|
||||
): estypes.QueryDslQueryContainer {
|
||||
): QueryDslQueryContainer {
|
||||
const children = node.arguments || [];
|
||||
|
||||
return {
|
||||
|
@ -34,3 +46,7 @@ export function toElasticsearchQuery(
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlOrFunctionNode): string {
|
||||
return `(${node.arguments.map(ast.toKqlExpression).join(' OR ')})`;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import { DataViewBase } from '../../..';
|
|||
import * as range from './range';
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { KQL_NODE_TYPE_LITERAL } from '../node_types/literal';
|
||||
import { KqlRangeFunctionNode } from './range';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -65,7 +66,12 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', 'gt', 1000);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'bytes',
|
||||
'gt',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -87,7 +93,12 @@ describe('kuery functions', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', 'gt', 1000);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'bytes',
|
||||
'gt',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toElasticsearchQuery(node);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -109,14 +120,24 @@ describe('kuery functions', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const node = nodeTypes.function.buildNode('range', 'byt*', 'gt', 1000);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'byt*',
|
||||
'gt',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
test('should support scripted fields', () => {
|
||||
const node = nodeTypes.function.buildNode('range', 'script number', 'gt', 1000);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'script number',
|
||||
'gt',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect((result.bool!.should as estypes.QueryDslQueryContainer[])[0]).toHaveProperty(
|
||||
|
@ -144,7 +165,7 @@ describe('kuery functions', () => {
|
|||
'@timestamp',
|
||||
'gt',
|
||||
'2018-01-03T19:04:17'
|
||||
);
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -172,7 +193,7 @@ describe('kuery functions', () => {
|
|||
'@timestamp',
|
||||
'gt',
|
||||
'2018-01-03T19:04:17'
|
||||
);
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toElasticsearchQuery(node, indexPattern, config);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -193,7 +214,12 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('range', 'bytes', 'gt', 1000);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'bytes',
|
||||
'gt',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toElasticsearchQuery(
|
||||
node,
|
||||
indexPattern,
|
||||
|
@ -225,7 +251,12 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('range', '*doublyNested*', 'lt', 8000);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'*doublyNested*',
|
||||
'lt',
|
||||
8000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
|
@ -253,7 +284,12 @@ describe('kuery functions', () => {
|
|||
minimum_should_match: 1,
|
||||
},
|
||||
};
|
||||
const node = nodeTypes.function.buildNode('range', '*doublyNested*', 'lt', 8000);
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'*doublyNested*',
|
||||
'lt',
|
||||
8000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toElasticsearchQuery(node, indexPattern, {
|
||||
nestedIgnoreUnmapped: true,
|
||||
});
|
||||
|
@ -261,5 +297,75 @@ describe('kuery functions', () => {
|
|||
expect(result).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
describe('operators', () => {
|
||||
test('gt', () => {
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'bytes',
|
||||
'gt',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"bytes > 1000"`);
|
||||
});
|
||||
|
||||
test('gte', () => {
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'bytes',
|
||||
'gte',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"bytes >= 1000"`);
|
||||
});
|
||||
|
||||
test('lt', () => {
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'bytes',
|
||||
'lt',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"bytes < 1000"`);
|
||||
});
|
||||
|
||||
test('lte', () => {
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'bytes',
|
||||
'lte',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"bytes <= 1000"`);
|
||||
});
|
||||
});
|
||||
|
||||
test('with wildcard field & literal value', () => {
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'byt*',
|
||||
'gt',
|
||||
1000
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"byt* > 1000"`);
|
||||
});
|
||||
|
||||
test('with wildcard field & wildcard value', () => {
|
||||
const node = nodeTypes.function.buildNode(
|
||||
'range',
|
||||
'byt*',
|
||||
'gt',
|
||||
'100*'
|
||||
) as KqlRangeFunctionNode;
|
||||
const result = range.toKqlExpression(node);
|
||||
expect(result).toMatchInlineSnapshot(`"byt* > 100*"`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -6,16 +6,38 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { nodeTypes } from '../node_types';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { buildNode as buildLiteralNode } from '../node_types/literal';
|
||||
import { type KqlFunctionNode, type KqlLiteralNode, nodeTypes } from '../node_types';
|
||||
import * as ast from '../ast';
|
||||
import { getRangeScript, RangeFilterParams } from '../../filters';
|
||||
import { getFields } from './utils/get_fields';
|
||||
import { getDataViewFieldSubtypeNested, getTimeZoneFromSettings } from '../../utils';
|
||||
import { getFullFieldNameNode } from './utils/get_full_field_name_node';
|
||||
import type { DataViewBase, KueryNode, KueryQueryOptions } from '../../..';
|
||||
import type { DataViewBase, KueryQueryOptions } from '../../..';
|
||||
import type { KqlContext } from '../types';
|
||||
|
||||
export const KQL_FUNCTION_RANGE = 'range';
|
||||
export const KQL_RANGE_OPERATOR_MAP = {
|
||||
gt: '>',
|
||||
gte: '>=',
|
||||
lt: '<',
|
||||
lte: '<=',
|
||||
};
|
||||
|
||||
export interface KqlRangeFunctionNode extends KqlFunctionNode {
|
||||
function: typeof KQL_FUNCTION_RANGE;
|
||||
arguments: [
|
||||
KqlLiteralNode,
|
||||
keyof Pick<RangeFilterParams, 'gt' | 'gte' | 'lt' | 'lte'>,
|
||||
KqlLiteralNode
|
||||
];
|
||||
}
|
||||
|
||||
export function isNode(node: KqlFunctionNode): node is KqlRangeFunctionNode {
|
||||
return node.function === KQL_FUNCTION_RANGE;
|
||||
}
|
||||
|
||||
export function buildNodeParams(
|
||||
fieldName: string,
|
||||
operator: keyof Pick<RangeFilterParams, 'gt' | 'gte' | 'lt' | 'lte'>,
|
||||
|
@ -23,16 +45,16 @@ export function buildNodeParams(
|
|||
) {
|
||||
// Run through the parser instead treating it as a literal because it may contain wildcards
|
||||
const fieldNameArg = ast.fromLiteralExpression(fieldName);
|
||||
const valueArg = nodeTypes.literal.buildNode(value);
|
||||
const valueArg = buildLiteralNode(value);
|
||||
return { arguments: [fieldNameArg, operator, valueArg] };
|
||||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
node: KqlRangeFunctionNode,
|
||||
indexPattern?: DataViewBase,
|
||||
config: KueryQueryOptions = {},
|
||||
context: KqlContext = {}
|
||||
): estypes.QueryDslQueryContainer {
|
||||
): QueryDslQueryContainer {
|
||||
const [fieldNameArg, operatorArg, valueArg] = node.arguments;
|
||||
const fullFieldNameArg = getFullFieldNameNode(
|
||||
fieldNameArg,
|
||||
|
@ -114,3 +136,10 @@ export function toElasticsearchQuery(
|
|||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlRangeFunctionNode): string {
|
||||
const [field, operator, value] = node.arguments;
|
||||
return `${ast.toKqlExpression(field)} ${KQL_RANGE_OPERATOR_MAP[operator]} ${ast.toKqlExpression(
|
||||
value
|
||||
)}`;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,6 @@ export const toElasticsearchQuery = (...params: Parameters<typeof astToElasticse
|
|||
|
||||
export { KQLSyntaxError } from './kuery_syntax_error';
|
||||
export { nodeTypes, nodeBuilder } from './node_types';
|
||||
export { fromKueryExpression } from './ast';
|
||||
export { fromKueryExpression, toKqlExpression } from './ast';
|
||||
export { escapeKuery } from './utils';
|
||||
export type { FunctionTypeBuildNode } from './node_types';
|
||||
export type { DslQuery, KueryNode, KueryQueryOptions, KueryParseOptions } from './types';
|
||||
|
|
|
@ -8,8 +8,16 @@
|
|||
|
||||
import { nodeTypes } from '.';
|
||||
|
||||
import { buildNode, buildNodeWithArgumentNodes, toElasticsearchQuery } from './function';
|
||||
import { toElasticsearchQuery as isFunctionToElasticsearchQuery } from '../functions/is';
|
||||
import {
|
||||
buildNode,
|
||||
buildNodeWithArgumentNodes,
|
||||
toElasticsearchQuery,
|
||||
toKqlExpression,
|
||||
} from './function';
|
||||
import {
|
||||
KqlIsFunctionNode,
|
||||
toElasticsearchQuery as isFunctionToElasticsearchQuery,
|
||||
} from '../functions/is';
|
||||
import { DataViewBase } from '../../es_query';
|
||||
import { fields } from '../../filters/stubs/fields.mocks';
|
||||
|
||||
|
@ -53,12 +61,20 @@ describe('kuery node types', () => {
|
|||
|
||||
describe('toElasticsearchQuery', () => {
|
||||
test("should return the given function type's ES query representation", () => {
|
||||
const node = buildNode('is', 'extension', 'jpg');
|
||||
const node = buildNode('is', 'extension', 'jpg') as KqlIsFunctionNode;
|
||||
const expected = isFunctionToElasticsearchQuery(node, indexPattern);
|
||||
const result = toElasticsearchQuery(node, indexPattern);
|
||||
|
||||
expect(expected).toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test("should return the given function type's KQL representation", () => {
|
||||
const node = buildNode('is', 'extension', 'jpg') as KqlIsFunctionNode;
|
||||
const result = toKqlExpression(node);
|
||||
expect(result).toEqual('extension: jpg');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,19 +8,48 @@
|
|||
|
||||
import _ from 'lodash';
|
||||
|
||||
import { functions } from '../functions';
|
||||
import {
|
||||
functions,
|
||||
KQL_FUNCTION_AND,
|
||||
KQL_FUNCTION_EXISTS,
|
||||
KQL_FUNCTION_NESTED,
|
||||
KQL_FUNCTION_IS,
|
||||
KQL_FUNCTION_NOT,
|
||||
KQL_FUNCTION_OR,
|
||||
KQL_FUNCTION_RANGE,
|
||||
} from '../functions';
|
||||
import type { DataViewBase, KueryNode, KueryQueryOptions } from '../../..';
|
||||
import type { FunctionName, FunctionTypeBuildNode } from './types';
|
||||
import type { KqlContext } from '../types';
|
||||
|
||||
export function buildNode(functionName: FunctionName, ...args: any[]) {
|
||||
export const KQL_NODE_TYPE_FUNCTION = 'function';
|
||||
|
||||
export type KqlFunctionName =
|
||||
| typeof KQL_FUNCTION_AND
|
||||
| typeof KQL_FUNCTION_EXISTS
|
||||
| typeof KQL_FUNCTION_IS
|
||||
| typeof KQL_FUNCTION_NESTED
|
||||
| typeof KQL_FUNCTION_NOT
|
||||
| typeof KQL_FUNCTION_OR
|
||||
| typeof KQL_FUNCTION_RANGE;
|
||||
|
||||
export interface KqlFunctionNode extends KueryNode {
|
||||
arguments: unknown[];
|
||||
function: KqlFunctionName;
|
||||
type: typeof KQL_NODE_TYPE_FUNCTION;
|
||||
}
|
||||
|
||||
export function isNode(node: KueryNode): node is KqlFunctionNode {
|
||||
return node.type === KQL_NODE_TYPE_FUNCTION;
|
||||
}
|
||||
|
||||
export function buildNode(functionName: KqlFunctionName, ...args: any[]): KqlFunctionNode {
|
||||
const kueryFunction = functions[functionName];
|
||||
if (_.isUndefined(kueryFunction)) {
|
||||
throw new Error(`Unknown function "${functionName}"`);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'function' as 'function',
|
||||
type: KQL_NODE_TYPE_FUNCTION,
|
||||
function: functionName,
|
||||
// This requires better typing of the different typings and their return types.
|
||||
// @ts-ignore
|
||||
|
@ -30,26 +59,50 @@ export function buildNode(functionName: FunctionName, ...args: any[]) {
|
|||
|
||||
// Mainly only useful in the grammar where we'll already have real argument nodes in hand
|
||||
export function buildNodeWithArgumentNodes(
|
||||
functionName: FunctionName,
|
||||
functionName: KqlFunctionName,
|
||||
args: any[]
|
||||
): FunctionTypeBuildNode {
|
||||
): KqlFunctionNode {
|
||||
if (_.isUndefined(functions[functionName])) {
|
||||
throw new Error(`Unknown function "${functionName}"`);
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'function',
|
||||
type: KQL_NODE_TYPE_FUNCTION,
|
||||
function: functionName,
|
||||
arguments: args,
|
||||
};
|
||||
}
|
||||
|
||||
export function toElasticsearchQuery(
|
||||
node: KueryNode,
|
||||
node: KqlFunctionNode,
|
||||
indexPattern?: DataViewBase,
|
||||
config?: KueryQueryOptions,
|
||||
context?: KqlContext
|
||||
) {
|
||||
const kueryFunction = functions[node.function as FunctionName];
|
||||
return kueryFunction.toElasticsearchQuery(node, indexPattern, config, context);
|
||||
if (functions.and.isNode(node))
|
||||
return functions.and.toElasticsearchQuery(node, indexPattern, config, context);
|
||||
if (functions.exists.isNode(node))
|
||||
return functions.exists.toElasticsearchQuery(node), indexPattern, config, context;
|
||||
if (functions.is.isNode(node))
|
||||
return functions.is.toElasticsearchQuery(node, indexPattern, config, context);
|
||||
if (functions.nested.isNode(node))
|
||||
return functions.nested.toElasticsearchQuery(node, indexPattern, config, context);
|
||||
if (functions.not.isNode(node))
|
||||
return functions.not.toElasticsearchQuery(node, indexPattern, config, context);
|
||||
if (functions.or.isNode(node))
|
||||
return functions.or.toElasticsearchQuery(node, indexPattern, config, context);
|
||||
if (functions.range.isNode(node))
|
||||
return functions.range.toElasticsearchQuery(node, indexPattern, config, context);
|
||||
throw new Error(`Unknown KQL function: "${node.function}"`);
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlFunctionNode): string {
|
||||
if (functions.and.isNode(node)) return functions.and.toKqlExpression(node);
|
||||
if (functions.exists.isNode(node)) return functions.exists.toKqlExpression(node);
|
||||
if (functions.is.isNode(node)) return functions.is.toKqlExpression(node);
|
||||
if (functions.nested.isNode(node)) return functions.nested.toKqlExpression(node);
|
||||
if (functions.not.isNode(node)) return functions.not.toKqlExpression(node);
|
||||
if (functions.or.isNode(node)) return functions.or.toKqlExpression(node);
|
||||
if (functions.range.isNode(node)) return functions.range.toKqlExpression(node);
|
||||
throw new Error(`Unknown KQL function: "${node.function}"`);
|
||||
}
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
import * as functionType from './function';
|
||||
import * as literal from './literal';
|
||||
import * as wildcard from './wildcard';
|
||||
import { FunctionTypeBuildNode } from './types';
|
||||
|
||||
export type { FunctionTypeBuildNode };
|
||||
export { nodeBuilder } from './node_builder';
|
||||
|
||||
export { type KqlFunctionNode, KQL_NODE_TYPE_FUNCTION } from './function';
|
||||
export { type KqlLiteralNode, KQL_NODE_TYPE_LITERAL } from './literal';
|
||||
export { type KqlWildcardNode, KQL_NODE_TYPE_WILDCARD } from './wildcard';
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { buildNode, KQL_NODE_TYPE_LITERAL, toElasticsearchQuery } from './literal';
|
||||
import { buildNode, KQL_NODE_TYPE_LITERAL, toElasticsearchQuery, toKqlExpression } from './literal';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -29,5 +29,19 @@ describe('kuery node types', () => {
|
|||
expect(result).toBe('foo');
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test('quoted', () => {
|
||||
const node = buildNode('foo');
|
||||
const result = toKqlExpression(node);
|
||||
expect(result).toBe('foo');
|
||||
});
|
||||
|
||||
test('unquoted', () => {
|
||||
const node = buildNode('foo', true);
|
||||
const result = toKqlExpression(node);
|
||||
expect(result).toBe('"foo"');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -33,3 +33,7 @@ export function buildNode(value: KqlLiteralType, isQuoted: boolean = false): Kql
|
|||
export function toElasticsearchQuery(node: KqlLiteralNode) {
|
||||
return node.value;
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlLiteralNode): string {
|
||||
return node.isQuoted ? `"${node.value}"` : `${node.value}`;
|
||||
}
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
/*
|
||||
* 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 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 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
/**
|
||||
* WARNING: these typings are incomplete
|
||||
*/
|
||||
|
||||
export type FunctionName = 'is' | 'and' | 'or' | 'not' | 'range' | 'exists' | 'nested';
|
||||
|
||||
export interface FunctionTypeBuildNode {
|
||||
type: 'function';
|
||||
function: FunctionName;
|
||||
// TODO -> Need to define a better type for DSL query
|
||||
arguments: any[];
|
||||
}
|
|
@ -7,14 +7,15 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
buildNode,
|
||||
KQL_WILDCARD_SYMBOL,
|
||||
hasLeadingWildcard,
|
||||
toElasticsearchQuery,
|
||||
test as testNode,
|
||||
toQueryStringQuery,
|
||||
type KqlWildcardNode,
|
||||
KQL_NODE_TYPE_WILDCARD,
|
||||
// @ts-ignore
|
||||
KQL_WILDCARD_SYMBOL,
|
||||
buildNode,
|
||||
hasLeadingWildcard,
|
||||
test as testNode,
|
||||
toElasticsearchQuery,
|
||||
toKqlExpression,
|
||||
toQueryStringQuery,
|
||||
} from './wildcard';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
@ -98,5 +99,16 @@ describe('kuery node types', () => {
|
|||
expect(hasLeadingWildcard(leadingWildcardNode)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('toKqlExpression', () => {
|
||||
test('should return the string representation of the wildcard literal', () => {
|
||||
const node: KqlWildcardNode = {
|
||||
type: 'wildcard',
|
||||
value: 'foo*bar@kuery-wildcard@baz',
|
||||
};
|
||||
const result = toKqlExpression(node);
|
||||
expect(result).toBe('foo\\*bar*baz');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -71,3 +71,7 @@ export function isLoneWildcard({ value }: KqlWildcardNode) {
|
|||
export function hasLeadingWildcard(node: KqlWildcardNode) {
|
||||
return !isLoneWildcard(node) && node.value.startsWith(KQL_WILDCARD_SYMBOL);
|
||||
}
|
||||
|
||||
export function toKqlExpression(node: KqlWildcardNode): string {
|
||||
return toQueryStringQuery(node);
|
||||
}
|
||||
|
|
|
@ -8,11 +8,15 @@
|
|||
|
||||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { SerializableRecord } from '@kbn/utility-types';
|
||||
import { KQL_NODE_TYPE_FUNCTION } from './node_types/function';
|
||||
import { KQL_NODE_TYPE_LITERAL } from './node_types/literal';
|
||||
import { KQL_NODE_TYPE_WILDCARD } from './node_types/wildcard';
|
||||
|
||||
/** @public */
|
||||
export type KqlNodeType = typeof KQL_NODE_TYPE_LITERAL | 'function' | typeof KQL_NODE_TYPE_WILDCARD;
|
||||
export type KqlNodeType =
|
||||
| typeof KQL_NODE_TYPE_LITERAL
|
||||
| typeof KQL_NODE_TYPE_FUNCTION
|
||||
| typeof KQL_NODE_TYPE_WILDCARD;
|
||||
|
||||
/** @public */
|
||||
export interface KueryNode {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue