Skip to content

Commit

Permalink
Support ES|QL requests through the NodeClient::execute (#106244)
Browse files Browse the repository at this point in the history
This commit adds support for executing ES|QL requests through the NodeClient::execute.

A subset of ES|QL's transport request and response APIs has been added x-pack/core. This offers basic functionality to run ES|QL queries without depending upon the ES|QL plugin directly. The API is deliberately small so as to not expose any unnecessary parts of the ES|QL implementation. It can be expanded later if and when needed, e.g. adding an explicitly Page, and Block types.
  • Loading branch information
ChrisHegarty committed Mar 17, 2024
1 parent 387eb38 commit aa6222a
Show file tree
Hide file tree
Showing 29 changed files with 741 additions and 34 deletions.
5 changes: 5 additions & 0 deletions docs/changelog/106244.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pr: 106244
summary: Support ES|QL requests through the `NodeClient::execute`
area: ES|QL
type: feature
issues: []
2 changes: 2 additions & 0 deletions x-pack/plugin/core/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
exports org.elasticsearch.xpack.core.enrich;
exports org.elasticsearch.xpack.core.eql;
exports org.elasticsearch.xpack.core.esql;
exports org.elasticsearch.xpack.core.esql.action;
exports org.elasticsearch.xpack.core.esql.action.internal; // TODO: qualify to esql when modularized
exports org.elasticsearch.xpack.core.frozen.action;
exports org.elasticsearch.xpack.core.frozen;
exports org.elasticsearch.xpack.core.graph.action;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

package org.elasticsearch.xpack.esql.action;
package org.elasticsearch.xpack.core.esql.action;

import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.esql.action;

import org.elasticsearch.action.ActionRequest;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.index.query.QueryBuilder;

import java.io.IOException;

public abstract class EsqlQueryRequest extends ActionRequest {

protected EsqlQueryRequest() {}

protected EsqlQueryRequest(StreamInput in) throws IOException {
super(in);
}

public abstract String query();

public abstract QueryBuilder filter();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.esql.action;

import org.elasticsearch.action.ActionRequestBuilder;
import org.elasticsearch.action.ActionType;
import org.elasticsearch.client.internal.ElasticsearchClient;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.xpack.core.esql.action.internal.SharedSecrets;

public abstract class EsqlQueryRequestBuilder<Request extends EsqlQueryRequest, Response extends EsqlQueryResponse> extends
ActionRequestBuilder<Request, Response> {

/** Creates a new ES|QL query request builder. */
public static EsqlQueryRequestBuilder<? extends EsqlQueryRequest, ? extends EsqlQueryResponse> newRequestBuilder(
ElasticsearchClient client
) {
return SharedSecrets.getEsqlQueryRequestBuilderAccess().newEsqlQueryRequestBuilder(client);
}

// not for direct use
protected EsqlQueryRequestBuilder(ElasticsearchClient client, ActionType<Response> action, Request request) {
super(client, action, request);
}

public abstract EsqlQueryRequestBuilder<Request, Response> query(String query);

public abstract EsqlQueryRequestBuilder<Request, Response> filter(QueryBuilder filter);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.esql.action;

import org.elasticsearch.action.ActionResponse;
import org.elasticsearch.core.Releasable;

/**
* Response to an ES|QL query request.
*
* This query response must be closed when the consumer of its response
* object is finished. Closing the query response closes and invalidates
* the response object. Calling {@link #response()} on a closed query
* response results in an IllegalStateException.
*/
public abstract class EsqlQueryResponse extends ActionResponse implements Releasable {

private boolean closed;

/** Returns the response object. */
public EsqlResponse response() {
if (closed) {
throw new IllegalStateException("closed");
}
return responseInternal();
}

protected abstract EsqlResponse responseInternal();

@Override
public void close() {
closed = true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.esql.action;

import org.elasticsearch.core.Releasable;

import java.util.List;

/**
* An ES|QL Response object.
*
* <p> Iterator based access to values of type T has the following properties:
* <ol>
* <li>single-value is of type {@code T}</li>
* <li>multi-value is of type {@code List<T>}</li>
* <li>absent value is {@code null}</li>
* </ol>
*
* <p> This response object should be closed when the consumer of its values
* is finished. Closing the response object invalidates any iterators of its
* values. An invalidated iterator, if not already exhausted, will eventually
* throw an IllegalStateException. Once a response object is closed, calling
* {@link #rows()}, {@link #column(int)}, or operating on an Iterable return
* from the aforementioned value accessor methods, results in an
* IllegalStateException.
*/
public interface EsqlResponse extends Releasable {

/** Returns the column info. */
List<? extends ColumnInfo> columns();

/**
* Returns an iterable that allows to iterator over the values in all rows
* of the response, this is the rows-iterator. A further iterator can be
* retrieved from the rows-iterator, which iterates over the actual values
* in the row, one row at a time, column-wise from left to right.
*/
Iterable<Iterable<Object>> rows();

/** Returns an iterable over the values in the given column. */
Iterable<Object> column(int columnIndex);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.esql.action.internal;

import org.elasticsearch.client.internal.ElasticsearchClient;
import org.elasticsearch.xpack.core.esql.action.EsqlQueryRequest;
import org.elasticsearch.xpack.core.esql.action.EsqlQueryRequestBuilder;
import org.elasticsearch.xpack.core.esql.action.EsqlQueryResponse;

/**
* For secret access to ES|QL internals only. Do not use.
* TODO qualify export when ES|QL is modularized
*/
public class SharedSecrets {

private static EsqlQueryRequestBuilderAccess esqlQueryRequestBuilderAccess;

public static void setEsqlQueryRequestBuilderAccess(EsqlQueryRequestBuilderAccess access) {
esqlQueryRequestBuilderAccess = access;
}

public static EsqlQueryRequestBuilderAccess getEsqlQueryRequestBuilderAccess() {
var access = esqlQueryRequestBuilderAccess;
if (access == null) {
throw new IllegalStateException("ESQL module not present or initialized");
}
return access;
}

public interface EsqlQueryRequestBuilderAccess {

EsqlQueryRequestBuilder<? extends EsqlQueryRequest, ? extends EsqlQueryResponse> newEsqlQueryRequestBuilder(
ElasticsearchClient client
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +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; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.core.esql.action;

import org.elasticsearch.test.ESIntegTestCase;

import static org.hamcrest.core.IsEqual.equalTo;

public class EsqlQueryRequestBuilderTests extends ESIntegTestCase {

// This is a trivial test that asserts IAE when the ES|QL module is
// not present.
public void testIllegalStateException() {
var e = expectThrows(IllegalStateException.class, () -> EsqlQueryRequestBuilder.newRequestBuilder(client()));
assertThat(e.getMessage(), equalTo("ESQL module not present or initialized"));
}
}
16 changes: 16 additions & 0 deletions x-pack/plugin/esql/qa/action/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apply plugin: 'elasticsearch.java'
apply plugin: 'elasticsearch.internal-cluster-test'

description = 'Tests for requests made through the Node Client request API'

dependencies {
api project(":test:framework")
api project(':server')
compileOnly project(path: xpackModule('core'))

testImplementation(testArtifact(project(xpackModule('core'))))
// runtime only - since the test source should not explicitly depend
// upon any types from ES|QL (only xpack core)
testImplementation project(':x-pack:plugin:ql')
testImplementation project(':x-pack:plugin:esql')
}

0 comments on commit aa6222a

Please sign in to comment.