mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[KQL] Clean up wildcard type (#134359)
* Clean up literal type * [KQL] Clean up wildcard type * [CI] Auto-commit changed files from 'node scripts/eslint --no-cache --fix' * Fix bugs Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
c477a90eca
commit
8e1773ea0a
16 changed files with 78 additions and 61 deletions
|
@ -12,7 +12,7 @@
|
|||
const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes;
|
||||
const buildLiteralNode = nodeTypes.literal.buildNode;
|
||||
const buildWildcardNode = nodeTypes.wildcard.buildNode;
|
||||
const { wildcardSymbol } = nodeTypes.wildcard;
|
||||
const { KQL_WILDCARD_SYMBOL } = nodeTypes.wildcard;
|
||||
}
|
||||
|
||||
start
|
||||
|
@ -189,7 +189,7 @@ Value "value"
|
|||
/ value:UnquotedLiteral {
|
||||
if (value.type === 'cursor') return value;
|
||||
|
||||
if (!allowLeadingWildcards && value.type === 'wildcard' && nodeTypes.wildcard.hasLeadingWildcard(value)) {
|
||||
if (!allowLeadingWildcards && nodeTypes.wildcard.isNode(value) && nodeTypes.wildcard.hasLeadingWildcard(value)) {
|
||||
error('Leading wildcards are disabled. See query:allowLeadingWildcards in Advanced Settings.');
|
||||
}
|
||||
|
||||
|
@ -248,7 +248,7 @@ UnquotedLiteral
|
|||
if (sequence === 'null') return buildLiteralNode(null);
|
||||
if (sequence === 'true') return buildLiteralNode(true);
|
||||
if (sequence === 'false') return buildLiteralNode(false);
|
||||
if (chars.includes(wildcardSymbol)) return buildWildcardNode(sequence);
|
||||
if (chars.includes(KQL_WILDCARD_SYMBOL)) return buildWildcardNode(sequence);
|
||||
return buildLiteralNode(sequence);
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ UnquotedCharacter
|
|||
/ !SpecialCharacter !Keyword !Cursor char:. { return char; }
|
||||
|
||||
Wildcard
|
||||
= '*' { return wildcardSymbol; }
|
||||
= '*' { return KQL_WILDCARD_SYMBOL; }
|
||||
|
||||
OptionalSpace
|
||||
= &{ return parseCursor; } prefix:Space* cursor:Cursor suffix:Space* {
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { nodeTypes } from '../node_types';
|
||||
import { fields } from '../../filters/stubs';
|
||||
import { DataViewBase } from '../..';
|
||||
import { KQL_NODE_TYPE_LITERAL } from '../node_types/literal';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -39,7 +40,7 @@ describe('kuery functions', () => {
|
|||
arguments: [arg],
|
||||
} = exists.buildNodeParams('response');
|
||||
|
||||
expect(arg).toHaveProperty('type', 'literal');
|
||||
expect(arg).toHaveProperty('type', KQL_NODE_TYPE_LITERAL);
|
||||
expect(arg).toHaveProperty('value', 'response');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -12,6 +12,8 @@ import { fields } from '../../filters/stubs';
|
|||
import * as is from './is';
|
||||
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';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -32,9 +34,9 @@ describe('kuery functions', () => {
|
|||
arguments: [fieldName, value],
|
||||
} = is.buildNodeParams('response', 200);
|
||||
|
||||
expect(fieldName).toHaveProperty('type', 'literal');
|
||||
expect(fieldName).toHaveProperty('type', KQL_NODE_TYPE_LITERAL);
|
||||
expect(fieldName).toHaveProperty('value', 'response');
|
||||
expect(value).toHaveProperty('type', 'literal');
|
||||
expect(value).toHaveProperty('type', KQL_NODE_TYPE_LITERAL);
|
||||
expect(value).toHaveProperty('value', 200);
|
||||
});
|
||||
|
||||
|
@ -43,8 +45,8 @@ describe('kuery functions', () => {
|
|||
arguments: [fieldName, value],
|
||||
} = is.buildNodeParams('machine*', 'win*');
|
||||
|
||||
expect(fieldName).toHaveProperty('type', 'wildcard');
|
||||
expect(value).toHaveProperty('type', 'wildcard');
|
||||
expect(fieldName).toHaveProperty('type', KQL_NODE_TYPE_WILDCARD);
|
||||
expect(value).toHaveProperty('type', KQL_NODE_TYPE_WILDCARD);
|
||||
});
|
||||
|
||||
test('should default to a non-phrase query', () => {
|
||||
|
|
|
@ -48,9 +48,8 @@ export function toElasticsearchQuery(
|
|||
arguments: [fieldNameArg, valueArg, isPhraseArg],
|
||||
} = node;
|
||||
|
||||
const isExistsQuery = valueArg.type === 'wildcard' && valueArg.value === wildcard.wildcardSymbol;
|
||||
const isAllFieldsQuery =
|
||||
fieldNameArg.type === 'wildcard' && fieldNameArg.value === wildcard.wildcardSymbol;
|
||||
const isExistsQuery = wildcard.isNode(valueArg) && wildcard.isLoneWildcard(valueArg);
|
||||
const isAllFieldsQuery = wildcard.isNode(fieldNameArg) && wildcard.isLoneWildcard(fieldNameArg);
|
||||
const isMatchAllQuery = isExistsQuery && isAllFieldsQuery;
|
||||
|
||||
if (isMatchAllQuery) {
|
||||
|
@ -65,7 +64,7 @@ export function toElasticsearchQuery(
|
|||
const value = !isUndefined(valueArg) ? ast.toElasticsearchQuery(valueArg) : valueArg;
|
||||
const type = isPhraseArg.value ? 'phrase' : 'best_fields';
|
||||
if (fullFieldNameArg.value === null) {
|
||||
if (valueArg.type === 'wildcard') {
|
||||
if (wildcard.isNode(valueArg)) {
|
||||
return {
|
||||
query_string: {
|
||||
query: wildcard.toQueryStringQuery(valueArg),
|
||||
|
@ -106,7 +105,7 @@ export function toElasticsearchQuery(
|
|||
// Wildcards can easily include nested and non-nested fields. There isn't a good way to let
|
||||
// users handle this themselves so we automatically add nested queries in this scenario.
|
||||
const subTypeNested = getDataViewFieldSubtypeNested(field);
|
||||
if (!(fullFieldNameArg.type === 'wildcard') || !subTypeNested?.nested || context?.nested) {
|
||||
if (!wildcard.isNode(fullFieldNameArg) || !subTypeNested?.nested || context?.nested) {
|
||||
return query;
|
||||
} else {
|
||||
return {
|
||||
|
@ -143,7 +142,7 @@ export function toElasticsearchQuery(
|
|||
},
|
||||
}),
|
||||
];
|
||||
} else if (valueArg.type === 'wildcard') {
|
||||
} else if (wildcard.isNode(valueArg)) {
|
||||
return [
|
||||
...accumulator,
|
||||
wrapWithNestedQuery({
|
||||
|
|
|
@ -12,6 +12,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';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -33,7 +34,7 @@ describe('kuery functions', () => {
|
|||
arguments: [fieldName],
|
||||
} = result;
|
||||
|
||||
expect(fieldName).toHaveProperty('type', 'literal');
|
||||
expect(fieldName).toHaveProperty('type', KQL_NODE_TYPE_LITERAL);
|
||||
expect(fieldName).toHaveProperty('value', 'bytes');
|
||||
});
|
||||
|
||||
|
@ -43,7 +44,7 @@ describe('kuery functions', () => {
|
|||
arguments: [, , valueArg],
|
||||
} = result;
|
||||
|
||||
expect(valueArg).toHaveProperty('type', 'literal');
|
||||
expect(valueArg).toHaveProperty('type', KQL_NODE_TYPE_LITERAL);
|
||||
expect(valueArg).toHaveProperty('value', 1000);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -59,7 +59,11 @@ export function toElasticsearchQuery(
|
|||
// Wildcards can easily include nested and non-nested fields. There isn't a good way to let
|
||||
// users handle this themselves so we automatically add nested queries in this scenario.
|
||||
const subTypeNested = getDataViewFieldSubtypeNested(field);
|
||||
if (!(fullFieldNameArg.type === 'wildcard') || !subTypeNested?.nested || context!.nested) {
|
||||
if (
|
||||
!nodeTypes.wildcard.isNode(fullFieldNameArg) ||
|
||||
!subTypeNested?.nested ||
|
||||
context!.nested
|
||||
) {
|
||||
return query;
|
||||
} else {
|
||||
return {
|
||||
|
|
|
@ -19,7 +19,7 @@ export function getFields(node: KueryNode, indexPattern?: DataViewBase) {
|
|||
return [];
|
||||
}
|
||||
return [field];
|
||||
} else if (node.type === 'wildcard') {
|
||||
} else if (wildcard.isNode(node)) {
|
||||
const fields = indexPattern.fields.filter((fld) => wildcard.test(node, fld.name));
|
||||
return fields;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
import { getFields } from './get_fields';
|
||||
import { DataViewBase, DataViewFieldBase, KueryNode } from '../../..';
|
||||
import { getDataViewFieldSubtypeNested } from '../../../utils';
|
||||
import { isNode as isWildcardNode } from '../../node_types/wildcard';
|
||||
|
||||
export function getFullFieldNameNode(
|
||||
rootNameNode: any,
|
||||
|
@ -23,7 +24,7 @@ export function getFullFieldNameNode(
|
|||
// Wildcards can easily include nested and non-nested fields. There isn't a good way to let
|
||||
// users handle this themselves so we automatically add nested queries in this scenario and skip the
|
||||
// error checking below.
|
||||
if (!indexPattern || (fullFieldNameNode.type === 'wildcard' && !nestedPath)) {
|
||||
if (!indexPattern || (isWildcardNode(fullFieldNameNode) && !nestedPath)) {
|
||||
return fullFieldNameNode;
|
||||
}
|
||||
const fields = getFields(fullFieldNameNode, indexPattern);
|
||||
|
|
|
@ -373,7 +373,7 @@ function peg$parse(input, options) {
|
|||
var peg$f16 = function(value) {
|
||||
if (value.type === 'cursor') return value;
|
||||
|
||||
if (!allowLeadingWildcards && value.type === 'wildcard' && nodeTypes.wildcard.hasLeadingWildcard(value)) {
|
||||
if (!allowLeadingWildcards && nodeTypes.wildcard.isNode(value) && nodeTypes.wildcard.hasLeadingWildcard(value)) {
|
||||
error('Leading wildcards are disabled. See query:allowLeadingWildcards in Advanced Settings.');
|
||||
}
|
||||
|
||||
|
@ -401,10 +401,10 @@ function peg$parse(input, options) {
|
|||
if (sequence === 'null') return buildLiteralNode(null);
|
||||
if (sequence === 'true') return buildLiteralNode(true);
|
||||
if (sequence === 'false') return buildLiteralNode(false);
|
||||
if (chars.includes(wildcardSymbol)) return buildWildcardNode(sequence);
|
||||
if (chars.includes(KQL_WILDCARD_SYMBOL)) return buildWildcardNode(sequence);
|
||||
return buildLiteralNode(sequence);
|
||||
};
|
||||
var peg$f22 = function() { return wildcardSymbol; };
|
||||
var peg$f22 = function() { return KQL_WILDCARD_SYMBOL; };
|
||||
var peg$f23 = function() { return '\t'; };
|
||||
var peg$f24 = function() { return '\r'; };
|
||||
var peg$f25 = function() { return '\n'; };
|
||||
|
@ -2189,7 +2189,7 @@ function peg$parse(input, options) {
|
|||
const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes;
|
||||
const buildLiteralNode = nodeTypes.literal.buildNode;
|
||||
const buildWildcardNode = nodeTypes.wildcard.buildNode;
|
||||
const { wildcardSymbol } = nodeTypes.wildcard;
|
||||
const { KQL_WILDCARD_SYMBOL } = nodeTypes.wildcard;
|
||||
|
||||
|
||||
peg$result = peg$startRuleFunction();
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import { buildNode, toElasticsearchQuery } from './literal';
|
||||
import { buildNode, KQL_NODE_TYPE_LITERAL, toElasticsearchQuery } from './literal';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -17,7 +16,7 @@ describe('kuery node types', () => {
|
|||
test('should return a node representing the given value', () => {
|
||||
const result = buildNode('foo');
|
||||
|
||||
expect(result).toHaveProperty('type', 'literal');
|
||||
expect(result).toHaveProperty('type', KQL_NODE_TYPE_LITERAL);
|
||||
expect(result).toHaveProperty('value', 'foo');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@ export function isNode(node: KueryNode): node is KqlLiteralNode {
|
|||
|
||||
export function buildNode(value: KqlLiteralType): KqlLiteralNode {
|
||||
return {
|
||||
type: 'literal',
|
||||
type: KQL_NODE_TYPE_LITERAL,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import { nodeBuilder } from './node_builder';
|
||||
import { toElasticsearchQuery } from '..';
|
||||
import { buildNode } from './literal';
|
||||
|
||||
jest.mock('../grammar');
|
||||
|
||||
|
@ -33,10 +34,7 @@ describe('nodeBuilder', () => {
|
|||
});
|
||||
|
||||
test('KueryNode value', () => {
|
||||
const literalValue = {
|
||||
type: 'literal' as 'literal',
|
||||
value: 'bar',
|
||||
};
|
||||
const literalValue = buildNode('bar');
|
||||
const nodes = nodeBuilder.is('foo', literalValue);
|
||||
const query = toElasticsearchQuery(nodes);
|
||||
expect(query).toMatchInlineSnapshot(`
|
||||
|
|
|
@ -18,8 +18,3 @@ export interface FunctionTypeBuildNode {
|
|||
// TODO -> Need to define a better type for DSL query
|
||||
arguments: any[];
|
||||
}
|
||||
|
||||
export interface WildcardTypeBuildNode {
|
||||
type: 'wildcard';
|
||||
value: string;
|
||||
}
|
||||
|
|
|
@ -8,11 +8,12 @@
|
|||
|
||||
import {
|
||||
buildNode,
|
||||
wildcardSymbol,
|
||||
KQL_WILDCARD_SYMBOL,
|
||||
hasLeadingWildcard,
|
||||
toElasticsearchQuery,
|
||||
test as testNode,
|
||||
toQueryStringQuery,
|
||||
KQL_NODE_TYPE_WILDCARD,
|
||||
// @ts-ignore
|
||||
} from './wildcard';
|
||||
|
||||
|
@ -22,18 +23,18 @@ describe('kuery node types', () => {
|
|||
describe('wildcard', () => {
|
||||
describe('buildNode', () => {
|
||||
test('should accept a string argument representing a wildcard string', () => {
|
||||
const wildcardValue = `foo${wildcardSymbol}bar`;
|
||||
const wildcardValue = `foo${KQL_WILDCARD_SYMBOL}bar`;
|
||||
const result = buildNode(wildcardValue);
|
||||
|
||||
expect(result).toHaveProperty('type', 'wildcard');
|
||||
expect(result).toHaveProperty('type', KQL_NODE_TYPE_WILDCARD);
|
||||
expect(result).toHaveProperty('value', wildcardValue);
|
||||
});
|
||||
|
||||
test('should accept and parse a wildcard string', () => {
|
||||
const result = buildNode('foo*bar');
|
||||
|
||||
expect(result).toHaveProperty('type', 'wildcard');
|
||||
expect(result.value).toBe(`foo${wildcardSymbol}bar`);
|
||||
expect(result).toHaveProperty('type', KQL_NODE_TYPE_WILDCARD);
|
||||
expect(result.value).toBe(`foo${KQL_WILDCARD_SYMBOL}bar`);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -6,11 +6,15 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { fromLiteralExpression } from '../ast/ast';
|
||||
import { WildcardTypeBuildNode } from './types';
|
||||
import { KueryNode } from '..';
|
||||
|
||||
export const wildcardSymbol = '@kuery-wildcard@';
|
||||
export const KQL_WILDCARD_SYMBOL = '@kuery-wildcard@';
|
||||
export const KQL_NODE_TYPE_WILDCARD = 'wildcard';
|
||||
|
||||
export interface KqlWildcardNode extends KueryNode {
|
||||
type: typeof KQL_NODE_TYPE_WILDCARD;
|
||||
value: string;
|
||||
}
|
||||
|
||||
// Copied from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions
|
||||
function escapeRegExp(str: string) {
|
||||
|
@ -22,37 +26,48 @@ function escapeQueryString(str: string) {
|
|||
return str.replace(/[+-=&|><!(){}[\]^"~*?:\\/]/g, '\\$&'); // $& means the whole matched string
|
||||
}
|
||||
|
||||
export function buildNode(value: string): WildcardTypeBuildNode | KueryNode {
|
||||
if (!value.includes(wildcardSymbol)) {
|
||||
return fromLiteralExpression(value);
|
||||
export function isNode(node: KueryNode): node is KqlWildcardNode {
|
||||
return node.type === KQL_NODE_TYPE_WILDCARD;
|
||||
}
|
||||
|
||||
export function isMatchAll(node: KqlWildcardNode) {
|
||||
return node.value === KQL_WILDCARD_SYMBOL;
|
||||
}
|
||||
|
||||
export function buildNode(value: string): KqlWildcardNode {
|
||||
// When called from the parser, all wildcard characters are replaced with a special flag (since escaped wildcards are
|
||||
// handled as normal strings). However, when invoking programmatically, callers shouldn't need to do this replacement.
|
||||
if (!value.includes(KQL_NODE_TYPE_WILDCARD) && value.includes('*')) {
|
||||
return buildNode(value.replaceAll('*', KQL_WILDCARD_SYMBOL));
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'wildcard',
|
||||
type: KQL_NODE_TYPE_WILDCARD,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
||||
export function test(node: any, str: string): boolean {
|
||||
export function test(node: KqlWildcardNode, str: string) {
|
||||
const { value } = node;
|
||||
const regex = value.split(wildcardSymbol).map(escapeRegExp).join('[\\s\\S]*');
|
||||
const regex = value.split(KQL_WILDCARD_SYMBOL).map(escapeRegExp).join('[\\s\\S]*');
|
||||
const regexp = new RegExp(`^${regex}$`);
|
||||
return regexp.test(str);
|
||||
}
|
||||
|
||||
export function toElasticsearchQuery(node: any): string {
|
||||
export function toElasticsearchQuery(node: KqlWildcardNode) {
|
||||
const { value } = node;
|
||||
return value.split(wildcardSymbol).join('*');
|
||||
return value.split(KQL_WILDCARD_SYMBOL).join('*');
|
||||
}
|
||||
|
||||
export function toQueryStringQuery(node: any): string {
|
||||
export function toQueryStringQuery(node: KqlWildcardNode) {
|
||||
const { value } = node;
|
||||
return value.split(wildcardSymbol).map(escapeQueryString).join('*');
|
||||
return value.split(KQL_WILDCARD_SYMBOL).map(escapeQueryString).join('*');
|
||||
}
|
||||
|
||||
export function hasLeadingWildcard(node: any): boolean {
|
||||
const { value } = node;
|
||||
// A lone wildcard turns into an `exists` query, so we're only concerned with
|
||||
// leading wildcards followed by additional characters.
|
||||
return value.startsWith(wildcardSymbol) && value.replace(wildcardSymbol, '').length > 0;
|
||||
export function isLoneWildcard({ value }: KqlWildcardNode) {
|
||||
return value.includes(KQL_WILDCARD_SYMBOL) && value.replace(KQL_WILDCARD_SYMBOL, '').length === 0;
|
||||
}
|
||||
|
||||
export function hasLeadingWildcard(node: KqlWildcardNode) {
|
||||
return !isLoneWildcard(node) && node.value.startsWith(KQL_WILDCARD_SYMBOL);
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@
|
|||
import * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
import { SerializableRecord } from '@kbn/utility-types';
|
||||
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' | 'wildcard';
|
||||
export type KqlNodeType = typeof KQL_NODE_TYPE_LITERAL | 'function' | typeof KQL_NODE_TYPE_WILDCARD;
|
||||
|
||||
/** @public */
|
||||
export interface KueryNode {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue