Skip to content

Commit

Permalink
esql-composer ideas
Browse files Browse the repository at this point in the history
  • Loading branch information
crespocarlos committed Dec 19, 2024
1 parent b53dc27 commit 6ccadc8
Show file tree
Hide file tree
Showing 23 changed files with 904 additions and 131 deletions.
9 changes: 5 additions & 4 deletions packages/kbn-esql-composer/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

export { from } from './src/commands/from';
Expand Down
9 changes: 5 additions & 4 deletions packages/kbn-esql-composer/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

module.exports = {
Expand Down
56 changes: 56 additions & 0 deletions packages/kbn-esql-composer/src/builder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { append } from './commands/append';
import {
QueryOperator,
BuilderCommand,
Params,
ChainedCommand,
QueryBuilderToOperator,
} from './types';

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

public abstract build(): ChainedCommand;

public toQueryOperator(): QueryOperator {
return append(this.build());
}

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

for (let i = 0; i < this.commands.length; i++) {
const currentCondition = this.commands[i];

if (i > 0) {
commandParts.push(currentCondition.type);
}

if (typeof currentCondition.command === 'function') {
const innerCommand = currentCondition.command().buildChain();
commandParts.push(
currentCondition.nested ? `(${innerCommand.command})` : innerCommand.command
);
bindingParts.push(innerCommand.bindings ?? []);
} else {
commandParts.push(currentCondition.command);
bindingParts.push(currentCondition.bindings ?? []);
}
}

return {
command: commandParts.join(' '),
bindings: bindingParts.flatMap((binding) => binding),
};
}
}
26 changes: 19 additions & 7 deletions packages/kbn-esql-composer/src/commands/append.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { Command, QueryOperator } from '../types';
import { isObject } from 'lodash';
import { Command, QueryOperator, Params, Query } from '../types';

export function append(command: Command | string): QueryOperator {
return (source) => {
export function append({
command,
bindings,
}: {
command: Command | string;
bindings?: 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,
};
};
}
10 changes: 6 additions & 4 deletions packages/kbn-esql-composer/src/commands/drop.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { drop } from './drop';
import { from } from './from';

Expand Down
23 changes: 13 additions & 10 deletions packages/kbn-esql-composer/src/commands/drop.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

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

export function drop(...columns: Array<string | string[]>) {
return append(
`DROP ${columns
.flatMap((column) => column)
.map((column) => escapeIdentifier(column))
.join(', ')}`
);
const command = `DROP ${columns
.flatMap((column) => column)
.map((column) => escapeIdentifier(column))
.join(', ')}`;

return append({
command,
});
}
57 changes: 57 additions & 0 deletions packages/kbn-esql-composer/src/commands/eval.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { evaluate } from './eval';
import { from } from './from';

describe('evaluate', () => {
const source = from('logs-*');

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

it('handles EVAL with params', () => {
const pipeline = source.pipe(
evaluate('hour = DATE_TRUNC(1 hour, ?ts)', {
ts: {
identifier: '@timestamp',
},
})
);

expect(pipeline.asString()).toEqual('FROM `logs-*`\n\t| EVAL hour = DATE_TRUNC(1 hour, ?ts)');
expect(pipeline.getBindings()).toEqual([
{
ts: {
identifier: '@timestamp',
},
},
]);
});
});
44 changes: 36 additions & 8 deletions packages/kbn-esql-composer/src/commands/eval.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,42 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { QueryOperator } from '../types';
import { append } from './append';
import { QueryBuilder } from '../builder';
import { ChainedCommand, Params } from '../types';

export function evaluate(body: string): QueryOperator {
return append(`EVAL ${body}`);
const EVAL = 'EVAL';

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

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

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

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

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

export function evaluate(body: string, bindings?: Params) {
return EvalBuilder.create(body, bindings);
}
10 changes: 6 additions & 4 deletions packages/kbn-esql-composer/src/commands/from.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { from } from './from';

describe('from', () => {
Expand Down
11 changes: 7 additions & 4 deletions packages/kbn-esql-composer/src/commands/from.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { createPipeline } from '../create_pipeline';
import type { QueryPipeline } from '../types';
import { escapeIdentifier } from '../utils/escape_identifier';
Expand All @@ -18,5 +20,6 @@ export function from(...patterns: Array<string | string[]>): QueryPipeline {
body: `FROM ${allPatterns.map((pattern) => escapeIdentifier(pattern)).join(',')}`,
},
],
bindings: [],
});
}
10 changes: 6 additions & 4 deletions packages/kbn-esql-composer/src/commands/keep.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { from } from './from';
import { keep } from './keep';

Expand Down
21 changes: 11 additions & 10 deletions packages/kbn-esql-composer/src/commands/keep.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
/*
* 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

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

export function keep(...columns: Array<string | string[]>) {
return append(
`KEEP ${columns
.flatMap((column) => column)
.map((column) => escapeIdentifier(column))
.join(', ')}`
);
const command = `KEEP ${columns
.flatMap((column) => column)
.map((column) => escapeIdentifier(column))
.join(', ')}`;

return append({ command });
}
11 changes: 6 additions & 5 deletions packages/kbn-esql-composer/src/commands/limit.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 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.
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import { append } from './append';

export function limit(value: number) {
return append(`LIMIT ${value}`);
return append({ command: `LIMIT ${value}` });
}
Loading

0 comments on commit 6ccadc8

Please sign in to comment.