Skip to content

Commit

Permalink
asString and adjustments
Browse files Browse the repository at this point in the history
  • Loading branch information
crespocarlos committed Dec 30, 2024
1 parent a0c8859 commit 91397b7
Show file tree
Hide file tree
Showing 20 changed files with 280 additions and 364 deletions.
12 changes: 6 additions & 6 deletions packages/kbn-esql-composer/src/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import {
BuilderCommand,
Params,
ChainedCommand,
QueryBuilderToOperator,
QueryOperatorConvertible,
} from './types';

export abstract class QueryBuilder implements QueryBuilderToOperator {
export abstract class QueryBuilder implements QueryOperatorConvertible {
protected readonly commands: BuilderCommand[] = [];

public abstract build(): ChainedCommand;
Expand All @@ -27,7 +27,7 @@ export abstract class QueryBuilder implements QueryBuilderToOperator {

protected buildChain(): ChainedCommand {
const commandParts: string[] = [];
const bindingParts: Params[] = [];
const paramsParts: Params[] = [];

for (let i = 0; i < this.commands.length; i++) {
const currentCondition = this.commands[i];
Expand All @@ -41,16 +41,16 @@ export abstract class QueryBuilder implements QueryBuilderToOperator {
commandParts.push(
currentCondition.nested ? `(${innerCommand.command})` : innerCommand.command
);
bindingParts.push(innerCommand.bindings ?? []);
paramsParts.push(innerCommand.params ?? []);
} else {
commandParts.push(currentCondition.command);
bindingParts.push(currentCondition.bindings ?? []);
paramsParts.push(currentCondition.params ?? []);
}
}

return {
command: commandParts.join(' '),
bindings: bindingParts.flatMap((binding) => binding),
params: paramsParts.flatMap((params) => params),
};
}
}
8 changes: 3 additions & 5 deletions packages/kbn-esql-composer/src/commands/append.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,18 @@ import { Command, QueryOperator, Params, Query } from '../types';

export function append({
command,
bindings,
params,
}: {
command: Command | string;
bindings?: Params;
params?: Params;
}): QueryOperator {
return (source): Query => {
const nextCommand = typeof command === 'string' ? { body: command } : command;

return {
...source,
commands: source.commands.concat(nextCommand),
bindings: !!bindings
? source.bindings.concat(isObject(bindings) ? bindings : [bindings])
: source.bindings,
params: !!params ? source.params.concat(isObject(params) ? params : [params]) : source.params,
};
};
}
14 changes: 8 additions & 6 deletions packages/kbn-esql-composer/src/commands/drop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ import { from } from './from';
describe('drop', () => {
const source = from('logs-*');
it('handles single strings', () => {
expect(source.pipe(drop('log.level', 'service.name')).asQuery()).toEqual(
'FROM `logs-*`\n\t| DROP `log.level`, `service.name`'
);
const pipeline = source.pipe(drop('log.level', 'service.name'));
const queryRequest = pipeline.asRequest();

expect(queryRequest.query).toEqual('FROM `logs-*`\n\t| DROP `log.level`, `service.name`');
});

it('handles arrays of strings', () => {
expect(source.pipe(drop(['log.level', 'service.name'])).asQuery()).toEqual(
'FROM `logs-*`\n\t| DROP `log.level`, `service.name`'
);
const pipeline = source.pipe(drop(['log.level', 'service.name']));
const queryRequest = pipeline.asRequest();

expect(queryRequest.query).toEqual('FROM `logs-*`\n\t| DROP `log.level`, `service.name`');
});
});
2 changes: 1 addition & 1 deletion packages/kbn-esql-composer/src/commands/drop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { escapeIdentifier } from '../utils/escape_identifier';
import { escapeIdentifier } from '../utils/formatters';
import { append } from './append';

export function drop(...columns: Array<string | string[]>) {
Expand Down
43 changes: 26 additions & 17 deletions packages/kbn-esql-composer/src/commands/eval.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,10 @@ describe('evaluate', () => {
const pipeline = source.pipe(
evaluate('type = CASE(languages <= 1, "monolingual",languages <= 2, "bilingual","polyglot")')
);
expect(pipeline.asQuery()).toEqual(
'FROM `logs-*`\n\t| EVAL type = CASE(languages <= 1, "monolingual",languages <= 2, "bilingual","polyglot")'
);
expect(pipeline.getBindings()).toEqual([]);
});

it('handles chained EVAL', () => {
const ids = ['host1', 'host2', 'host3'];
const pipeline = source.pipe(
evaluate('entity.type = ?', 'host')
.concat('entity.display_name = COALESCE(?, entity.id)', 'some_host')
.concat(`entity.id = CONCAT(${ids.map(() => '?').join()})`, ids)
);
expect(pipeline.asQuery()).toEqual(
'FROM `logs-*`\n\t| EVAL entity.type = ?, entity.display_name = COALESCE(?, entity.id), entity.id = CONCAT(?,?,?)'
expect(pipeline.asString()).toEqual(
'FROM `logs-*`\n\t| EVAL type = CASE(languages <= 1, "monolingual",languages <= 2, "bilingual","polyglot")'
);
expect(pipeline.getBindings()).toEqual(['host', 'some_host', 'host1', 'host2', 'host3']);
});

it('handles EVAL with params', () => {
Expand All @@ -44,14 +31,36 @@ describe('evaluate', () => {
},
})
);
const queryRequest = pipeline.asRequest();

expect(pipeline.asQuery()).toEqual('FROM `logs-*`\n\t| EVAL hour = DATE_TRUNC(1 hour, ?ts)');
expect(pipeline.getBindings()).toEqual([
expect(queryRequest.query).toEqual('FROM `logs-*`\n\t| EVAL hour = DATE_TRUNC(1 hour, ?ts)');
expect(queryRequest.params).toEqual([
{
ts: {
identifier: '@timestamp',
},
},
]);
expect(pipeline.asString()).toEqual(
'FROM `logs-*`\n\t| EVAL hour = DATE_TRUNC(1 hour, `@timestamp`)'
);
});

it('handles chained EVAL with params', () => {
const ids = ['aws', 'host1'];
const pipeline = source.pipe(
evaluate('entity.type = ?', 'host')
.concat('entity.display_name = COALESCE(?, entity.id)', 'some_host')
.concat(`entity.id = CONCAT(${ids.map(() => '?').join()})`, ids)
);
const queryRequest = pipeline.asRequest();

expect(queryRequest.query).toEqual(
'FROM `logs-*`\n\t| EVAL entity.type = ?, entity.display_name = COALESCE(?, entity.id), entity.id = CONCAT(?,?)'
);
expect(queryRequest.params).toEqual(['host', 'some_host', 'aws', 'host1']);
expect(pipeline.asString()).toEqual(
'FROM `logs-*`\n\t| EVAL entity.type = "host", entity.display_name = COALESCE("some_host", entity.id), entity.id = CONCAT("aws","host1")'
);
});
});
20 changes: 10 additions & 10 deletions packages/kbn-esql-composer/src/commands/eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,30 @@ import { ChainedCommand, Params } from '../types';
const EVAL = 'EVAL';

class EvalBuilder extends QueryBuilder {
private constructor(body: string, bindings?: Params) {
private constructor(body: string, params?: Params) {
super();
this.commands.push({ command: body, bindings, type: EVAL });
this.commands.push({ command: body, params, type: EVAL });
}

public static create(body: string, bindings?: Params) {
return new EvalBuilder(body, bindings);
public static create(body: string, params?: Params) {
return new EvalBuilder(body, params);
}

public concat(body: string, bindings?: Params) {
this.commands.push({ command: body, bindings, type: EVAL });
public concat(body: string, params?: Params) {
this.commands.push({ command: body, params, type: EVAL });
return this;
}

public build(): ChainedCommand {
const { command, bindings } = this.buildChain();
const { command, params } = this.buildChain();

return {
command: `${EVAL} ${command.replace(/\s+EVAL/g, ',')}`,
bindings,
params,
};
}
}

export function evaluate(body: string, bindings?: Params) {
return EvalBuilder.create(body, bindings);
export function evaluate(body: string, params?: Params) {
return EvalBuilder.create(body, params);
}
4 changes: 2 additions & 2 deletions packages/kbn-esql-composer/src/commands/from.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import { from } from './from';

describe('from', () => {
it('handles single strings', () => {
expect(from('logs-*', 'traces-*').asQuery()).toEqual('FROM `logs-*`,`traces-*`');
expect(from('logs-*', 'traces-*').asString()).toEqual('FROM `logs-*`,`traces-*`');
});

it('handles arrays of strings', () => {
expect(from(['logs-*', 'traces-*']).asQuery()).toEqual('FROM `logs-*`,`traces-*`');
expect(from(['logs-*', 'traces-*']).asString()).toEqual('FROM `logs-*`,`traces-*`');
});
});
4 changes: 2 additions & 2 deletions packages/kbn-esql-composer/src/commands/from.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

import { createPipeline } from '../create_pipeline';
import type { QueryPipeline } from '../types';
import { escapeIdentifier } from '../utils/escape_identifier';
import { escapeIdentifier } from '../utils/formatters';

export function from(...patterns: Array<string | string[]>): QueryPipeline {
const allPatterns = patterns.flatMap((pattern) => pattern);
Expand All @@ -20,6 +20,6 @@ export function from(...patterns: Array<string | string[]>): QueryPipeline {
body: `FROM ${allPatterns.map((pattern) => escapeIdentifier(pattern)).join(',')}`,
},
],
bindings: [],
params: [],
});
}
18 changes: 10 additions & 8 deletions packages/kbn-esql-composer/src/commands/keep.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ import { keep } from './keep';

describe('keep', () => {
const source = from('logs-*');
it('handles single strings', () => {
expect(source.pipe(keep('log.level', 'service.name')).asQuery()).toEqual(
'FROM `logs-*`\n\t| KEEP `log.level`, `service.name`'
);
it('should build KEEP from single strings', () => {
const pipeline = source.pipe(keep('log.level', 'service.name'));
const queryRequest = pipeline.asRequest();

expect(queryRequest.query).toEqual('FROM `logs-*`\n\t| KEEP `log.level`, `service.name`');
});

it('handles arrays of strings', () => {
expect(source.pipe(keep(['log.level', 'service.name'])).asQuery()).toEqual(
'FROM `logs-*`\n\t| KEEP `log.level`, `service.name`'
);
it('should build KEEP from array of strings', () => {
const pipeline = source.pipe(keep(['log.level', 'service.name']));
const queryRequest = pipeline.asRequest();

expect(queryRequest.query).toEqual('FROM `logs-*`\n\t| KEEP `log.level`, `service.name`');
});
});
2 changes: 1 addition & 1 deletion packages/kbn-esql-composer/src/commands/keep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { escapeIdentifier } from '../utils/escape_identifier';
import { escapeIdentifier } from '../utils/formatters';
import { append } from './append';

export function keep(...columns: Array<string | string[]>) {
Expand Down
37 changes: 23 additions & 14 deletions packages/kbn-esql-composer/src/commands/sort.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,45 +15,50 @@ describe('sort', () => {

it('handles single strings', () => {
const pipeline = source.pipe(sort('@timestamp', 'log.level'));
expect(pipeline.asQuery()).toEqual('FROM `logs-*`\n\t| SORT @timestamp ASC, log.level ASC');
expect(pipeline.getBindings()).toEqual([]);

expect(pipeline.asString()).toEqual(
'FROM `logs-*`\n\t| SORT `@timestamp` ASC, `log.level` ASC'
);
});

it('handles SORT with SortOrder', () => {
const pipeline = source.pipe(sort({ '@timestamp': SortOrder.Desc }));
expect(pipeline.asQuery()).toEqual('FROM `logs-*`\n\t| SORT @timestamp DESC');
expect(pipeline.getBindings()).toEqual([]);

expect(pipeline.asString()).toEqual('FROM `logs-*`\n\t| SORT `@timestamp` DESC');
});

it('handles a mix of strings and SortOrder instructions', () => {
const pipeline = source.pipe(sort('@timestamp', { 'log.level': SortOrder.Desc }));

expect(pipeline.asQuery()).toEqual('FROM `logs-*`\n\t| SORT @timestamp ASC, log.level DESC');
expect(pipeline.getBindings()).toEqual([]);
expect(pipeline.asString()).toEqual(
'FROM `logs-*`\n\t| SORT `@timestamp` ASC, `log.level` DESC'
);
});

it('handles nested sort arrays', () => {
it('handles sort arrays', () => {
const pipeline = source.pipe(sort(['@timestamp', { 'log.level': SortOrder.Asc }]));
expect(pipeline.asQuery()).toEqual('FROM `logs-*`\n\t| SORT @timestamp ASC, log.level ASC');
expect(pipeline.getBindings()).toEqual([]);

expect(pipeline.asString()).toEqual(
'FROM `logs-*`\n\t| SORT `@timestamp` ASC, `log.level` ASC'
);
});

it('handles SORT with params', () => {
const pipeline = source.pipe(
sortRaw('?timestamp DESC, ?logLevel ASC', {
env: {
timestamp: {
identifier: '@timestamp',
},
logLevel: {
identifier: 'log.level',
},
})
);

expect(pipeline.asQuery()).toEqual('FROM `logs-*`\n\t| SORT ?timestamp DESC, ?logLevel ASC');
expect(pipeline.getBindings()).toEqual([
const queryRequest = pipeline.asRequest();
expect(queryRequest.query).toEqual('FROM `logs-*`\n\t| SORT ?timestamp DESC, ?logLevel ASC');
expect(queryRequest.params).toEqual([
{
env: {
timestamp: {
identifier: '@timestamp',
},
},
Expand All @@ -63,5 +68,9 @@ describe('sort', () => {
},
},
]);

expect(pipeline.asString()).toEqual(
'FROM `logs-*`\n\t| SORT `@timestamp` DESC, `log.level` ASC'
);
});
});
7 changes: 4 additions & 3 deletions packages/kbn-esql-composer/src/commands/sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
*/

import { NamedParameterWithIdentifier, QueryOperator } from '../types';
import { formatColumn } from '../utils/formatters';
import { append } from './append';

export enum SortOrder {
Expand All @@ -20,8 +21,8 @@ type Sort = Record<string, SortOrder>;
type SortArgs = Sort | string | Array<Sort | string>;

// TODO: a better name?
export function sortRaw(body: string, bindings?: NamedParameterWithIdentifier): QueryOperator {
return append({ command: `SORT ${body}`, bindings });
export function sortRaw(body: string, params?: NamedParameterWithIdentifier): QueryOperator {
return append({ command: `SORT ${body}`, params });
}

export function sort(...sorts: SortArgs[]): QueryOperator {
Expand All @@ -40,7 +41,7 @@ export function sort(...sorts: SortArgs[]): QueryOperator {
});

const command = `SORT ${allSorts
.map((sortInstruction) => `${sortInstruction.column} ${sortInstruction.order}`)
.map((sortInstruction) => `${formatColumn(sortInstruction.column)} ${sortInstruction.order}`)
.join(', ')}`;

return append({ command });
Expand Down
Loading

0 comments on commit 91397b7

Please sign in to comment.