Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ES|QL] AST node synthesizer #201814

Merged
merged 18 commits into from
Dec 2, 2024

Conversation

vadimkibana
Copy link
Contributor

@vadimkibana vadimkibana commented Nov 26, 2024

Summary

Closes #191812

Adds ability to create ES|QL AST nodes from plain strings and compose them.

Create an integer literal:

const node = expr `42`;

Create any expression:

const node = expr `nested.field = fn(123)`;

Compose AST nodes:

const value = expr `123`;
const node = expr `nested.field = fn(${value})`;

Usage

You can create an assignment expression AST node as simle as:

import { synth } from '@kbn/esql-ast';

const node = synth.expr `my.field = max(10, ?my_param)`;
// { type: 'function', name: '=', args: [ ... ]}

To construct an equivalent AST node using the Builder class, you would need to
write the following code:

import { Builder } from '@kbn/esql-ast';

const node = Builder.expression.func.binary('=', [
  Builder.expression.column({
    args: [Builder.identifier({ name: 'my' }), Builder.identifier({ name: 'field' })],
  }),
  Builder.expression.func.call('max', [
    Builder.expression.literal.integer(10),
    Builder.param.named({ value: 'my_param' }),
  ]),
]);
// { type: 'function', name: '=', args: [ ... ]}

You can nest template strings to create more complex AST nodes:

const field = synth.expr `my.field`;
const value = synth.expr `max(10, 20)`;

const assignment = synth.expr`${field} = ${value}`;
// { type: 'function', name: '=', args: [ 
//     { type: 'column', args: [ ... ] },
//     { type: 'function', name: 'max', args: [ ... ] }
// ]}

Use the synth.cmd method to create command nodes:

const command = synth.cmd `WHERE my.field == 10`;
// { type: 'command', name: 'WHERE', args: [ ... ]}

AST nodes created by the synthesizer are pretty-printed when you coerce them to
a string or call the toString method:

const command = synth.cmd ` WHERE my.field == 10 `; // { type: 'command', ... }
String(command); // "WHERE my.field == 10"

Reference

synth.expr

The synth.expr synthesizes an expression AST nodes. (Expressions are
basically any thing that can go into a WHERE command, like boolean expression,
function call, literal, params, etc.)

Use it as a function:

const node = synth.expr('my.field = max(10, 20)');

Specify parser options:

const node = synth.expr('my.field = max(10, 20)', { withFormatting: false });

Use it as a template string tag:

const node = synth.expr `my.field = max(10, 20)`;

Specify parser options, when using as a template string tag:

const node = synth.expr({ withFormatting: false }) `my.field = max(10, 20)`;

Combine nodes using template strings:

const field = synth.expr `my.field`;
const node = synth.expr `${field} = max(10, 20)`;

Print the node as a string:

const node = synth.expr `my.field = max(10, 20)`;
String(node); // 'my.field = max(10, 20)'

synth.cmd

The synth.cmd synthesizes a command AST node (such as SELECT, WHERE,
etc.). You use it the same as the synth.expr function or template string tag.
The only difference is that the synth.cmd function or tag creates a command
AST node.

const node = synth.cmd `WHERE my.field == 10`;
// { type: 'command', name: 'where', args: [ ... ]}

Checklist

Delete any items that are not applicable to this PR.

For maintainers

@vadimkibana vadimkibana marked this pull request as ready for review November 28, 2024 09:16
@vadimkibana vadimkibana requested a review from a team as a code owner November 28, 2024 09:16
@vadimkibana vadimkibana added review release_note:skip Skip the PR/issue when compiling release notes v9.0.0 Feature:ES|QL ES|QL related features in Kibana Team:ESQL ES|QL related features in Kibana v8.18.0 labels Nov 28, 2024
@elasticmachine
Copy link
Contributor

Pinging @elastic/kibana-esql (Team:ESQL)

@vadimkibana vadimkibana added the backport:prev-minor Backport to (8.x) the previous minor version (i.e. one version back from main) label Nov 28, 2024
@vadimkibana vadimkibana enabled auto-merge (squash) November 28, 2024 09:18
auto-merge was automatically disabled November 28, 2024 09:32

Head branch was pushed to by a user without write access

@vadimkibana vadimkibana changed the title [ES|QL] AST node generator [ES|QL] AST node synthesizer Nov 28, 2024
@vadimkibana vadimkibana enabled auto-merge (squash) November 28, 2024 14:10
@elasticmachine
Copy link
Contributor

💚 Build Succeeded

Metrics [docs]

Page load bundle

Size of the bundles that are downloaded on every page load. Target size is below 100kb

id before after diff
kbnUiSharedDeps-srcJs 3.5MB 3.5MB +1008.0B

History

@stratoula stratoula added backport:version Backport to applied version labels and removed backport:prev-minor Backport to (8.x) the previous minor version (i.e. one version back from main) labels Dec 2, 2024
Copy link
Contributor

@stratoula stratoula left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is lovely! LGTM

@vadimkibana vadimkibana merged commit 378002f into elastic:main Dec 2, 2024
11 checks passed
@kibanamachine
Copy link
Contributor

Starting backport for target branches: 8.x

https://github.com/elastic/kibana/actions/runs/12116127168

kibanamachine pushed a commit to kibanamachine/kibana that referenced this pull request Dec 2, 2024
## Summary

Closes elastic#191812

Adds ability to create ES|QL AST nodes from plain strings and compose
them.

Create an integer literal:

```js
const node = expr `42`;
```

Create any expression:

```js
const node = expr `nested.field = fn(123)`;
```

Compose AST nodes:

```js
const value = expr `123`;
const node = expr `nested.field = fn(${value})`;
```

## Usage

You can create an assignment expression AST node as simle as:

```ts
import { synth } from '@kbn/esql-ast';

const node = synth.expr `my.field = max(10, ?my_param)`;
// { type: 'function', name: '=', args: [ ... ]}
```

To construct an equivalent AST node using the `Builder` class, you would
need to
write the following code:

```ts
import { Builder } from '@kbn/esql-ast';

const node = Builder.expression.func.binary('=', [
  Builder.expression.column({
    args: [Builder.identifier({ name: 'my' }), Builder.identifier({ name: 'field' })],
  }),
  Builder.expression.func.call('max', [
    Builder.expression.literal.integer(10),
    Builder.param.named({ value: 'my_param' }),
  ]),
]);
// { type: 'function', name: '=', args: [ ... ]}
```

You can nest template strings to create more complex AST nodes:

```ts
const field = synth.expr `my.field`;
const value = synth.expr `max(10, 20)`;

const assignment = synth.expr`${field} = ${value}`;
// { type: 'function', name: '=', args: [
//     { type: 'column', args: [ ... ] },
//     { type: 'function', name: 'max', args: [ ... ] }
// ]}
```

Use the `synth.cmd` method to create command nodes:

```ts
const command = synth.cmd `WHERE my.field == 10`;
// { type: 'command', name: 'WHERE', args: [ ... ]}
```

AST nodes created by the synthesizer are pretty-printed when you coerce
them to
a string or call the `toString` method:

```ts
const command = synth.cmd ` WHERE my.field == 10 `; // { type: 'command', ... }
String(command); // "WHERE my.field == 10"
```

## Reference

### `synth.expr`

The `synth.expr` synthesizes an expression AST nodes. (*Expressions* are
basically any thing that can go into a `WHERE` command, like boolean
expression,
function call, literal, params, etc.)

Use it as a function:

```ts
const node = synth.expr('my.field = max(10, 20)');
```

Specify parser options:

```ts
const node = synth.expr('my.field = max(10, 20)', { withFormatting: false });
```

Use it as a template string tag:

```ts
const node = synth.expr `my.field = max(10, 20)`;
```

Specify parser options, when using as a template string tag:

```ts
const node = synth.expr({ withFormatting: false }) `my.field = max(10, 20)`;
```

Combine nodes using template strings:

```ts
const field = synth.expr `my.field`;
const node = synth.expr `${field} = max(10, 20)`;
```

Print the node as a string:

```ts
const node = synth.expr `my.field = max(10, 20)`;
String(node); // 'my.field = max(10, 20)'
```

### `synth.cmd`

The `synth.cmd` synthesizes a command AST node (such as `SELECT`,
`WHERE`,
etc.). You use it the same as the `synth.expr` function or template
string tag.
The only difference is that the `synth.cmd` function or tag creates a
command
AST node.

```ts
const node = synth.cmd `WHERE my.field == 10`;
// { type: 'command', name: 'where', args: [ ... ]}
```

### Checklist

Delete any items that are not applicable to this PR.

- [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

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)

(cherry picked from commit 378002f)
@kibanamachine
Copy link
Contributor

💚 All backports created successfully

Status Branch Result
8.x

Note: Successful backport PRs will be merged automatically after passing CI.

Questions ?

Please refer to the Backport tool documentation

kibanamachine added a commit that referenced this pull request Dec 2, 2024
# Backport

This will backport the following commits from `main` to `8.x`:
- [[ES|QL] AST node synthesizer
(#201814)](#201814)

<!--- Backport version: 9.4.3 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Vadim
Kibana","email":"[email protected]"},"sourceCommit":{"committedDate":"2024-12-02T09:12:36Z","message":"[ES|QL]
AST node synthesizer (#201814)\n\n## Summary\r\n\r\nCloses
https://github.com/elastic/kibana/issues/191812\r\n\r\nAdds ability to
create ES|QL AST nodes from plain strings and
compose\r\nthem.\r\n\r\nCreate an integer literal:\r\n\r\n```js\r\nconst
node = expr `42`;\r\n```\r\n\r\nCreate any
expression:\r\n\r\n```js\r\nconst node = expr `nested.field =
fn(123)`;\r\n```\r\n\r\nCompose AST nodes:\r\n\r\n```js\r\nconst value =
expr `123`;\r\nconst node = expr `nested.field =
fn(${value})`;\r\n```\r\n\r\n## Usage\r\n\r\nYou can create an
assignment expression AST node as simle as:\r\n\r\n```ts\r\nimport {
synth } from '@kbn/esql-ast';\r\n\r\nconst node = synth.expr `my.field =
max(10, ?my_param)`;\r\n// { type: 'function', name: '=', args: [ ...
]}\r\n```\r\n\r\nTo construct an equivalent AST node using the `Builder`
class, you would\r\nneed to\r\nwrite the following
code:\r\n\r\n```ts\r\nimport { Builder } from
'@kbn/esql-ast';\r\n\r\nconst node = Builder.expression.func.binary('=',
[\r\n Builder.expression.column({\r\n args: [Builder.identifier({ name:
'my' }), Builder.identifier({ name: 'field' })],\r\n }),\r\n
Builder.expression.func.call('max', [\r\n
Builder.expression.literal.integer(10),\r\n Builder.param.named({ value:
'my_param' }),\r\n ]),\r\n]);\r\n// { type: 'function', name: '=', args:
[ ... ]}\r\n```\r\n\r\nYou can nest template strings to create more
complex AST nodes:\r\n\r\n```ts\r\nconst field = synth.expr
`my.field`;\r\nconst value = synth.expr `max(10, 20)`;\r\n\r\nconst
assignment = synth.expr`${field} = ${value}`;\r\n// { type: 'function',
name: '=', args: [ \r\n// { type: 'column', args: [ ... ] },\r\n// {
type: 'function', name: 'max', args: [ ... ] }\r\n//
]}\r\n```\r\n\r\nUse the `synth.cmd` method to create command
nodes:\r\n\r\n```ts\r\nconst command = synth.cmd `WHERE my.field ==
10`;\r\n// { type: 'command', name: 'WHERE', args: [ ...
]}\r\n```\r\n\r\nAST nodes created by the synthesizer are pretty-printed
when you coerce\r\nthem to\r\na string or call the `toString`
method:\r\n\r\n```ts\r\nconst command = synth.cmd ` WHERE my.field == 10
`; // { type: 'command', ... }\r\nString(command); // \"WHERE my.field
== 10\"\r\n```\r\n\r\n\r\n## Reference\r\n\r\n###
`synth.expr`\r\n\r\nThe `synth.expr` synthesizes an expression AST
nodes. (*Expressions* are\r\nbasically any thing that can go into a
`WHERE` command, like boolean\r\nexpression,\r\nfunction call, literal,
params, etc.)\r\n\r\nUse it as a function:\r\n\r\n```ts\r\nconst node =
synth.expr('my.field = max(10, 20)');\r\n```\r\n\r\nSpecify parser
options:\r\n\r\n```ts\r\nconst node = synth.expr('my.field = max(10,
20)', { withFormatting: false });\r\n```\r\n\r\nUse it as a template
string tag:\r\n\r\n```ts\r\nconst node = synth.expr `my.field = max(10,
20)`;\r\n```\r\n\r\nSpecify parser options, when using as a template
string tag:\r\n\r\n```ts\r\nconst node = synth.expr({ withFormatting:
false }) `my.field = max(10, 20)`;\r\n```\r\n\r\nCombine nodes using
template strings:\r\n\r\n```ts\r\nconst field = synth.expr
`my.field`;\r\nconst node = synth.expr `${field} = max(10,
20)`;\r\n```\r\n\r\nPrint the node as a string:\r\n\r\n```ts\r\nconst
node = synth.expr `my.field = max(10, 20)`;\r\nString(node); //
'my.field = max(10, 20)'\r\n```\r\n\r\n\r\n### `synth.cmd`\r\n\r\nThe
`synth.cmd` synthesizes a command AST node (such as
`SELECT`,\r\n`WHERE`,\r\netc.). You use it the same as the `synth.expr`
function or template\r\nstring tag.\r\nThe only difference is that the
`synth.cmd` function or tag creates a\r\ncommand\r\nAST
node.\r\n\r\n```ts\r\nconst node = synth.cmd `WHERE my.field ==
10`;\r\n// { type: 'command', name: 'where', args: [ ...
]}\r\n```\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are
not applicable to this PR.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n### For
maintainers\r\n\r\n- [x] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)","sha":"378002f9f60356d1d5fe04a6fdf0a7641175ca14","branchLabelMapping":{"^v9.0.0$":"main","^v8.18.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["review","release_note:skip","v9.0.0","Feature:ES|QL","Team:ESQL","backport:version","v8.18.0"],"title":"[ES|QL]
AST node
synthesizer","number":201814,"url":"https://github.com/elastic/kibana/pull/201814","mergeCommit":{"message":"[ES|QL]
AST node synthesizer (#201814)\n\n## Summary\r\n\r\nCloses
https://github.com/elastic/kibana/issues/191812\r\n\r\nAdds ability to
create ES|QL AST nodes from plain strings and
compose\r\nthem.\r\n\r\nCreate an integer literal:\r\n\r\n```js\r\nconst
node = expr `42`;\r\n```\r\n\r\nCreate any
expression:\r\n\r\n```js\r\nconst node = expr `nested.field =
fn(123)`;\r\n```\r\n\r\nCompose AST nodes:\r\n\r\n```js\r\nconst value =
expr `123`;\r\nconst node = expr `nested.field =
fn(${value})`;\r\n```\r\n\r\n## Usage\r\n\r\nYou can create an
assignment expression AST node as simle as:\r\n\r\n```ts\r\nimport {
synth } from '@kbn/esql-ast';\r\n\r\nconst node = synth.expr `my.field =
max(10, ?my_param)`;\r\n// { type: 'function', name: '=', args: [ ...
]}\r\n```\r\n\r\nTo construct an equivalent AST node using the `Builder`
class, you would\r\nneed to\r\nwrite the following
code:\r\n\r\n```ts\r\nimport { Builder } from
'@kbn/esql-ast';\r\n\r\nconst node = Builder.expression.func.binary('=',
[\r\n Builder.expression.column({\r\n args: [Builder.identifier({ name:
'my' }), Builder.identifier({ name: 'field' })],\r\n }),\r\n
Builder.expression.func.call('max', [\r\n
Builder.expression.literal.integer(10),\r\n Builder.param.named({ value:
'my_param' }),\r\n ]),\r\n]);\r\n// { type: 'function', name: '=', args:
[ ... ]}\r\n```\r\n\r\nYou can nest template strings to create more
complex AST nodes:\r\n\r\n```ts\r\nconst field = synth.expr
`my.field`;\r\nconst value = synth.expr `max(10, 20)`;\r\n\r\nconst
assignment = synth.expr`${field} = ${value}`;\r\n// { type: 'function',
name: '=', args: [ \r\n// { type: 'column', args: [ ... ] },\r\n// {
type: 'function', name: 'max', args: [ ... ] }\r\n//
]}\r\n```\r\n\r\nUse the `synth.cmd` method to create command
nodes:\r\n\r\n```ts\r\nconst command = synth.cmd `WHERE my.field ==
10`;\r\n// { type: 'command', name: 'WHERE', args: [ ...
]}\r\n```\r\n\r\nAST nodes created by the synthesizer are pretty-printed
when you coerce\r\nthem to\r\na string or call the `toString`
method:\r\n\r\n```ts\r\nconst command = synth.cmd ` WHERE my.field == 10
`; // { type: 'command', ... }\r\nString(command); // \"WHERE my.field
== 10\"\r\n```\r\n\r\n\r\n## Reference\r\n\r\n###
`synth.expr`\r\n\r\nThe `synth.expr` synthesizes an expression AST
nodes. (*Expressions* are\r\nbasically any thing that can go into a
`WHERE` command, like boolean\r\nexpression,\r\nfunction call, literal,
params, etc.)\r\n\r\nUse it as a function:\r\n\r\n```ts\r\nconst node =
synth.expr('my.field = max(10, 20)');\r\n```\r\n\r\nSpecify parser
options:\r\n\r\n```ts\r\nconst node = synth.expr('my.field = max(10,
20)', { withFormatting: false });\r\n```\r\n\r\nUse it as a template
string tag:\r\n\r\n```ts\r\nconst node = synth.expr `my.field = max(10,
20)`;\r\n```\r\n\r\nSpecify parser options, when using as a template
string tag:\r\n\r\n```ts\r\nconst node = synth.expr({ withFormatting:
false }) `my.field = max(10, 20)`;\r\n```\r\n\r\nCombine nodes using
template strings:\r\n\r\n```ts\r\nconst field = synth.expr
`my.field`;\r\nconst node = synth.expr `${field} = max(10,
20)`;\r\n```\r\n\r\nPrint the node as a string:\r\n\r\n```ts\r\nconst
node = synth.expr `my.field = max(10, 20)`;\r\nString(node); //
'my.field = max(10, 20)'\r\n```\r\n\r\n\r\n### `synth.cmd`\r\n\r\nThe
`synth.cmd` synthesizes a command AST node (such as
`SELECT`,\r\n`WHERE`,\r\netc.). You use it the same as the `synth.expr`
function or template\r\nstring tag.\r\nThe only difference is that the
`synth.cmd` function or tag creates a\r\ncommand\r\nAST
node.\r\n\r\n```ts\r\nconst node = synth.cmd `WHERE my.field ==
10`;\r\n// { type: 'command', name: 'where', args: [ ...
]}\r\n```\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are
not applicable to this PR.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n### For
maintainers\r\n\r\n- [x] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)","sha":"378002f9f60356d1d5fe04a6fdf0a7641175ca14"}},"sourceBranch":"main","suggestedTargetBranches":["8.x"],"targetPullRequestStates":[{"branch":"main","label":"v9.0.0","branchLabelMappingKey":"^v9.0.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/201814","number":201814,"mergeCommit":{"message":"[ES|QL]
AST node synthesizer (#201814)\n\n## Summary\r\n\r\nCloses
https://github.com/elastic/kibana/issues/191812\r\n\r\nAdds ability to
create ES|QL AST nodes from plain strings and
compose\r\nthem.\r\n\r\nCreate an integer literal:\r\n\r\n```js\r\nconst
node = expr `42`;\r\n```\r\n\r\nCreate any
expression:\r\n\r\n```js\r\nconst node = expr `nested.field =
fn(123)`;\r\n```\r\n\r\nCompose AST nodes:\r\n\r\n```js\r\nconst value =
expr `123`;\r\nconst node = expr `nested.field =
fn(${value})`;\r\n```\r\n\r\n## Usage\r\n\r\nYou can create an
assignment expression AST node as simle as:\r\n\r\n```ts\r\nimport {
synth } from '@kbn/esql-ast';\r\n\r\nconst node = synth.expr `my.field =
max(10, ?my_param)`;\r\n// { type: 'function', name: '=', args: [ ...
]}\r\n```\r\n\r\nTo construct an equivalent AST node using the `Builder`
class, you would\r\nneed to\r\nwrite the following
code:\r\n\r\n```ts\r\nimport { Builder } from
'@kbn/esql-ast';\r\n\r\nconst node = Builder.expression.func.binary('=',
[\r\n Builder.expression.column({\r\n args: [Builder.identifier({ name:
'my' }), Builder.identifier({ name: 'field' })],\r\n }),\r\n
Builder.expression.func.call('max', [\r\n
Builder.expression.literal.integer(10),\r\n Builder.param.named({ value:
'my_param' }),\r\n ]),\r\n]);\r\n// { type: 'function', name: '=', args:
[ ... ]}\r\n```\r\n\r\nYou can nest template strings to create more
complex AST nodes:\r\n\r\n```ts\r\nconst field = synth.expr
`my.field`;\r\nconst value = synth.expr `max(10, 20)`;\r\n\r\nconst
assignment = synth.expr`${field} = ${value}`;\r\n// { type: 'function',
name: '=', args: [ \r\n// { type: 'column', args: [ ... ] },\r\n// {
type: 'function', name: 'max', args: [ ... ] }\r\n//
]}\r\n```\r\n\r\nUse the `synth.cmd` method to create command
nodes:\r\n\r\n```ts\r\nconst command = synth.cmd `WHERE my.field ==
10`;\r\n// { type: 'command', name: 'WHERE', args: [ ...
]}\r\n```\r\n\r\nAST nodes created by the synthesizer are pretty-printed
when you coerce\r\nthem to\r\na string or call the `toString`
method:\r\n\r\n```ts\r\nconst command = synth.cmd ` WHERE my.field == 10
`; // { type: 'command', ... }\r\nString(command); // \"WHERE my.field
== 10\"\r\n```\r\n\r\n\r\n## Reference\r\n\r\n###
`synth.expr`\r\n\r\nThe `synth.expr` synthesizes an expression AST
nodes. (*Expressions* are\r\nbasically any thing that can go into a
`WHERE` command, like boolean\r\nexpression,\r\nfunction call, literal,
params, etc.)\r\n\r\nUse it as a function:\r\n\r\n```ts\r\nconst node =
synth.expr('my.field = max(10, 20)');\r\n```\r\n\r\nSpecify parser
options:\r\n\r\n```ts\r\nconst node = synth.expr('my.field = max(10,
20)', { withFormatting: false });\r\n```\r\n\r\nUse it as a template
string tag:\r\n\r\n```ts\r\nconst node = synth.expr `my.field = max(10,
20)`;\r\n```\r\n\r\nSpecify parser options, when using as a template
string tag:\r\n\r\n```ts\r\nconst node = synth.expr({ withFormatting:
false }) `my.field = max(10, 20)`;\r\n```\r\n\r\nCombine nodes using
template strings:\r\n\r\n```ts\r\nconst field = synth.expr
`my.field`;\r\nconst node = synth.expr `${field} = max(10,
20)`;\r\n```\r\n\r\nPrint the node as a string:\r\n\r\n```ts\r\nconst
node = synth.expr `my.field = max(10, 20)`;\r\nString(node); //
'my.field = max(10, 20)'\r\n```\r\n\r\n\r\n### `synth.cmd`\r\n\r\nThe
`synth.cmd` synthesizes a command AST node (such as
`SELECT`,\r\n`WHERE`,\r\netc.). You use it the same as the `synth.expr`
function or template\r\nstring tag.\r\nThe only difference is that the
`synth.cmd` function or tag creates a\r\ncommand\r\nAST
node.\r\n\r\n```ts\r\nconst node = synth.cmd `WHERE my.field ==
10`;\r\n// { type: 'command', name: 'where', args: [ ...
]}\r\n```\r\n\r\n\r\n\r\n### Checklist\r\n\r\nDelete any items that are
not applicable to this PR.\r\n\r\n- [x] [Unit or
functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere
updated or added to match the most common scenarios\r\n\r\n### For
maintainers\r\n\r\n- [x] This was checked for breaking API changes and
was
[labeled\r\nappropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)","sha":"378002f9f60356d1d5fe04a6fdf0a7641175ca14"}},{"branch":"8.x","label":"v8.18.0","branchLabelMappingKey":"^v8.18.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Vadim Kibana <[email protected]>
CAWilson94 pushed a commit to CAWilson94/kibana that referenced this pull request Dec 9, 2024
## Summary

Closes elastic#191812

Adds ability to create ES|QL AST nodes from plain strings and compose
them.

Create an integer literal:

```js
const node = expr `42`;
```

Create any expression:

```js
const node = expr `nested.field = fn(123)`;
```

Compose AST nodes:

```js
const value = expr `123`;
const node = expr `nested.field = fn(${value})`;
```

## Usage

You can create an assignment expression AST node as simle as:

```ts
import { synth } from '@kbn/esql-ast';

const node = synth.expr `my.field = max(10, ?my_param)`;
// { type: 'function', name: '=', args: [ ... ]}
```

To construct an equivalent AST node using the `Builder` class, you would
need to
write the following code:

```ts
import { Builder } from '@kbn/esql-ast';

const node = Builder.expression.func.binary('=', [
  Builder.expression.column({
    args: [Builder.identifier({ name: 'my' }), Builder.identifier({ name: 'field' })],
  }),
  Builder.expression.func.call('max', [
    Builder.expression.literal.integer(10),
    Builder.param.named({ value: 'my_param' }),
  ]),
]);
// { type: 'function', name: '=', args: [ ... ]}
```

You can nest template strings to create more complex AST nodes:

```ts
const field = synth.expr `my.field`;
const value = synth.expr `max(10, 20)`;

const assignment = synth.expr`${field} = ${value}`;
// { type: 'function', name: '=', args: [ 
//     { type: 'column', args: [ ... ] },
//     { type: 'function', name: 'max', args: [ ... ] }
// ]}
```

Use the `synth.cmd` method to create command nodes:

```ts
const command = synth.cmd `WHERE my.field == 10`;
// { type: 'command', name: 'WHERE', args: [ ... ]}
```

AST nodes created by the synthesizer are pretty-printed when you coerce
them to
a string or call the `toString` method:

```ts
const command = synth.cmd ` WHERE my.field == 10 `; // { type: 'command', ... }
String(command); // "WHERE my.field == 10"
```


## Reference

### `synth.expr`

The `synth.expr` synthesizes an expression AST nodes. (*Expressions* are
basically any thing that can go into a `WHERE` command, like boolean
expression,
function call, literal, params, etc.)

Use it as a function:

```ts
const node = synth.expr('my.field = max(10, 20)');
```

Specify parser options:

```ts
const node = synth.expr('my.field = max(10, 20)', { withFormatting: false });
```

Use it as a template string tag:

```ts
const node = synth.expr `my.field = max(10, 20)`;
```

Specify parser options, when using as a template string tag:

```ts
const node = synth.expr({ withFormatting: false }) `my.field = max(10, 20)`;
```

Combine nodes using template strings:

```ts
const field = synth.expr `my.field`;
const node = synth.expr `${field} = max(10, 20)`;
```

Print the node as a string:

```ts
const node = synth.expr `my.field = max(10, 20)`;
String(node); // 'my.field = max(10, 20)'
```


### `synth.cmd`

The `synth.cmd` synthesizes a command AST node (such as `SELECT`,
`WHERE`,
etc.). You use it the same as the `synth.expr` function or template
string tag.
The only difference is that the `synth.cmd` function or tag creates a
command
AST node.

```ts
const node = synth.cmd `WHERE my.field == 10`;
// { type: 'command', name: 'where', args: [ ... ]}
```



### Checklist

Delete any items that are not applicable to this PR.

- [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

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)
CAWilson94 pushed a commit to CAWilson94/kibana that referenced this pull request Dec 12, 2024
## Summary

Closes elastic#191812

Adds ability to create ES|QL AST nodes from plain strings and compose
them.

Create an integer literal:

```js
const node = expr `42`;
```

Create any expression:

```js
const node = expr `nested.field = fn(123)`;
```

Compose AST nodes:

```js
const value = expr `123`;
const node = expr `nested.field = fn(${value})`;
```

## Usage

You can create an assignment expression AST node as simle as:

```ts
import { synth } from '@kbn/esql-ast';

const node = synth.expr `my.field = max(10, ?my_param)`;
// { type: 'function', name: '=', args: [ ... ]}
```

To construct an equivalent AST node using the `Builder` class, you would
need to
write the following code:

```ts
import { Builder } from '@kbn/esql-ast';

const node = Builder.expression.func.binary('=', [
  Builder.expression.column({
    args: [Builder.identifier({ name: 'my' }), Builder.identifier({ name: 'field' })],
  }),
  Builder.expression.func.call('max', [
    Builder.expression.literal.integer(10),
    Builder.param.named({ value: 'my_param' }),
  ]),
]);
// { type: 'function', name: '=', args: [ ... ]}
```

You can nest template strings to create more complex AST nodes:

```ts
const field = synth.expr `my.field`;
const value = synth.expr `max(10, 20)`;

const assignment = synth.expr`${field} = ${value}`;
// { type: 'function', name: '=', args: [ 
//     { type: 'column', args: [ ... ] },
//     { type: 'function', name: 'max', args: [ ... ] }
// ]}
```

Use the `synth.cmd` method to create command nodes:

```ts
const command = synth.cmd `WHERE my.field == 10`;
// { type: 'command', name: 'WHERE', args: [ ... ]}
```

AST nodes created by the synthesizer are pretty-printed when you coerce
them to
a string or call the `toString` method:

```ts
const command = synth.cmd ` WHERE my.field == 10 `; // { type: 'command', ... }
String(command); // "WHERE my.field == 10"
```


## Reference

### `synth.expr`

The `synth.expr` synthesizes an expression AST nodes. (*Expressions* are
basically any thing that can go into a `WHERE` command, like boolean
expression,
function call, literal, params, etc.)

Use it as a function:

```ts
const node = synth.expr('my.field = max(10, 20)');
```

Specify parser options:

```ts
const node = synth.expr('my.field = max(10, 20)', { withFormatting: false });
```

Use it as a template string tag:

```ts
const node = synth.expr `my.field = max(10, 20)`;
```

Specify parser options, when using as a template string tag:

```ts
const node = synth.expr({ withFormatting: false }) `my.field = max(10, 20)`;
```

Combine nodes using template strings:

```ts
const field = synth.expr `my.field`;
const node = synth.expr `${field} = max(10, 20)`;
```

Print the node as a string:

```ts
const node = synth.expr `my.field = max(10, 20)`;
String(node); // 'my.field = max(10, 20)'
```


### `synth.cmd`

The `synth.cmd` synthesizes a command AST node (such as `SELECT`,
`WHERE`,
etc.). You use it the same as the `synth.expr` function or template
string tag.
The only difference is that the `synth.cmd` function or tag creates a
command
AST node.

```ts
const node = synth.cmd `WHERE my.field == 10`;
// { type: 'command', name: 'where', args: [ ... ]}
```



### Checklist

Delete any items that are not applicable to this PR.

- [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

### For maintainers

- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#_add_your_labels)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport:version Backport to applied version labels Feature:ES|QL ES|QL related features in Kibana release_note:skip Skip the PR/issue when compiling release notes review Team:ESQL ES|QL related features in Kibana v8.18.0 v9.0.0
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[ES|QL] High-level AST mutation API
4 participants