From 5a3180fea85687dc9467717ce46c09b2adb71d5b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 24 Oct 2025 15:54:41 +0000 Subject: [PATCH] Publish internal modules separately for downstream reuse (#4484) Co-authored-by: Louis Chu Co-authored-by: Chen Dai Co-authored-by: Mebsina (cherry picked from commit 05d6594c5163378cae45d37d03984439d5edd98e) Signed-off-by: github-actions[bot] --- .github/workflows/maven-publish-modules.yml | 47 ++++ api/README.md | 75 ++++++ api/build.gradle | 71 ++++++ .../sql/api/EmptyDataSourceService.java | 51 ++++ .../sql/api/UnifiedQueryPlanner.java | 227 ++++++++++++++++++ .../sql/api/UnifiedQueryPlannerTest.java | 182 ++++++++++++++ build.gradle | 48 ++++ settings.gradle | 1 + 8 files changed, 702 insertions(+) create mode 100644 .github/workflows/maven-publish-modules.yml create mode 100644 api/README.md create mode 100644 api/build.gradle create mode 100644 api/src/main/java/org/opensearch/sql/api/EmptyDataSourceService.java create mode 100644 api/src/main/java/org/opensearch/sql/api/UnifiedQueryPlanner.java create mode 100644 api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java diff --git a/.github/workflows/maven-publish-modules.yml b/.github/workflows/maven-publish-modules.yml new file mode 100644 index 00000000000..64743356e97 --- /dev/null +++ b/.github/workflows/maven-publish-modules.yml @@ -0,0 +1,47 @@ +name: Publish unified query modules to maven + +on: + workflow_dispatch: + push: + branches: + - main + - '[0-9]+.[0-9]+' + - '[0-9]+.x' + +env: + SNAPSHOT_REPO_URL: https://ci.opensearch.org/ci/dbc/snapshots/maven/ + +jobs: + publish-unified-query-modules: + strategy: + fail-fast: false + if: github.repository == 'opensearch-project/sql' + runs-on: ubuntu-latest + + permissions: + id-token: write + contents: write + + steps: + - uses: actions/setup-java@v3 + with: + distribution: temurin # Temurin is a distribution of adoptium + java-version: 21 + - uses: actions/checkout@v3 + - name: Load secret + uses: 1password/load-secrets-action@v2 + with: + # Export loaded secrets as environment variables + export-env: true + env: + OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }} + MAVEN_SNAPSHOTS_S3_REPO: op://opensearch-infra-secrets/maven-snapshots-s3/repo + MAVEN_SNAPSHOTS_S3_ROLE: op://opensearch-infra-secrets/maven-snapshots-s3/role + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v5 + with: + role-to-assume: ${{ env.MAVEN_SNAPSHOTS_S3_ROLE }} + aws-region: us-east-1 + - name: publish snapshots to maven + run: | + ./gradlew publishUnifiedQueryPublicationToSnapshotsRepository diff --git a/api/README.md b/api/README.md new file mode 100644 index 00000000000..0288b7ad22c --- /dev/null +++ b/api/README.md @@ -0,0 +1,75 @@ +# Unified Query API + +This module provides a high-level integration layer for the Calcite-based query engine, enabling external systems such as Apache Spark or command-line tools to parse and analyze queries without exposing low-level internals. + +## Overview + +The `UnifiedQueryPlanner` serves as the primary entry point for external consumers. It accepts PPL (Piped Processing Language) queries and returns Calcite `RelNode` logical plans as intermediate representation. + +## Usage + +Use the declarative, fluent builder API to initialize the `UnifiedQueryPlanner`. + +```java +UnifiedQueryPlanner planner = UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("opensearch", schema) + .defaultNamespace("opensearch") + .cacheMetadata(true) + .build(); + +RelNode plan = planner.plan("source = opensearch.test"); +``` + +## Development & Testing + +A set of unit tests is provided to validate planner behavior. + +To run tests: + +``` +./gradlew :api:test +``` + +## Integration Guide + +This guide walks through how to integrate unified query planner into your application. + +### Step 1: Add Dependency + +The module is currently published as a snapshot to the AWS Sonatype Snapshots repository. To include it as a dependency in your project, add the following to your `pom.xml` or `build.gradle`: + +```xml + + org.opensearch.query + unified-query-api + YOUR_VERSION_HERE + +``` + +### Step 2: Implement a Calcite Schema + +You must implement the Calcite `Schema` interface and register them using the fluent `catalog()` method on the builder. + +```java +public class MySchema extends AbstractSchema { + @Override + protected Map getTableMap() { + return Map.of( + "test_table", + new AbstractTable() { + @Override + public RelDataType getRowType(RelDataTypeFactory typeFactory) { + return typeFactory.createStructType( + List.of(typeFactory.createSqlType(SqlTypeName.INTEGER)), + List.of("id")); + } + }); + } +} +``` + +## Future Work + +- Expand support to SQL language. +- Extend planner to generate optimized physical plans using Calcite's optimization frameworks. diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 00000000000..717086a5ce5 --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,71 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +plugins { + id 'java-library' + id 'jacoco' + id 'com.diffplug.spotless' +} + +dependencies { + api project(':ppl') + + testImplementation group: 'junit', name: 'junit', version: '4.13.2' + testImplementation group: 'org.hamcrest', name: 'hamcrest-library', version: "${hamcrest_version}" + testImplementation group: 'org.mockito', name: 'mockito-core', version: "${mockito_version}" + testImplementation group: 'org.apache.calcite', name: 'calcite-testkit', version: '1.38.0' +} + +spotless { + java { + target fileTree('.') { + include '**/*.java' + exclude '**/build/**', '**/build-*/**', 'src/main/gen/**' + } + importOrder() + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + googleJavaFormat('1.17.0').reflowLongStrings().groupArtifact('com.google.googlejavaformat:google-java-format') + } +} + +test { + testLogging { + events "passed", "skipped", "failed" + exceptionFormat "full" + } +} + +jacocoTestReport { + reports { + html.required = true + xml.required = true + } + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, + exclude: ['**/antlr/parser/**']) + })) + } +} +test.finalizedBy(project.tasks.jacocoTestReport) +jacocoTestCoverageVerification { + violationRules { + rule { + limit { + minimum = 0.9 + } + + } + } + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, + exclude: ['**/antlr/parser/**']) + })) + } +} +check.dependsOn jacocoTestCoverageVerification diff --git a/api/src/main/java/org/opensearch/sql/api/EmptyDataSourceService.java b/api/src/main/java/org/opensearch/sql/api/EmptyDataSourceService.java new file mode 100644 index 00000000000..0fa0c38ad3c --- /dev/null +++ b/api/src/main/java/org/opensearch/sql/api/EmptyDataSourceService.java @@ -0,0 +1,51 @@ +package org.opensearch.sql.api; + +import java.util.Map; +import java.util.Set; +import org.opensearch.sql.datasource.DataSourceService; +import org.opensearch.sql.datasource.RequestContext; +import org.opensearch.sql.datasource.model.DataSource; +import org.opensearch.sql.datasource.model.DataSourceMetadata; + +/** A DataSourceService that assumes no access to data sources */ +public class EmptyDataSourceService implements DataSourceService { + public EmptyDataSourceService() {} + + @Override + public DataSource getDataSource(String dataSourceName) { + return null; + } + + @Override + public Set getDataSourceMetadata(boolean isDefaultDataSourceRequired) { + return Set.of(); + } + + @Override + public DataSourceMetadata getDataSourceMetadata(String name) { + return null; + } + + @Override + public void createDataSource(DataSourceMetadata metadata) {} + + @Override + public void updateDataSource(DataSourceMetadata dataSourceMetadata) {} + + @Override + public void patchDataSource(Map dataSourceData) {} + + @Override + public void deleteDataSource(String dataSourceName) {} + + @Override + public Boolean dataSourceExists(String dataSourceName) { + return false; + } + + @Override + public DataSourceMetadata verifyDataSourceAccessAndGetRawMetadata( + String dataSourceName, RequestContext context) { + return null; + } +} diff --git a/api/src/main/java/org/opensearch/sql/api/UnifiedQueryPlanner.java b/api/src/main/java/org/opensearch/sql/api/UnifiedQueryPlanner.java new file mode 100644 index 00000000000..6a524ec307a --- /dev/null +++ b/api/src/main/java/org/opensearch/sql/api/UnifiedQueryPlanner.java @@ -0,0 +1,227 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.api; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.antlr.v4.runtime.tree.ParseTree; +import org.apache.calcite.jdbc.CalciteSchema; +import org.apache.calcite.plan.RelTraitDef; +import org.apache.calcite.rel.RelCollation; +import org.apache.calcite.rel.RelCollations; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.core.Sort; +import org.apache.calcite.rel.logical.LogicalSort; +import org.apache.calcite.rel.metadata.DefaultRelMetadataProvider; +import org.apache.calcite.schema.Schema; +import org.apache.calcite.schema.SchemaPlus; +import org.apache.calcite.sql.parser.SqlParser; +import org.apache.calcite.tools.FrameworkConfig; +import org.apache.calcite.tools.Frameworks; +import org.apache.calcite.tools.Programs; +import org.opensearch.sql.ast.statement.Query; +import org.opensearch.sql.ast.statement.Statement; +import org.opensearch.sql.ast.tree.UnresolvedPlan; +import org.opensearch.sql.calcite.CalcitePlanContext; +import org.opensearch.sql.calcite.CalciteRelNodeVisitor; +import org.opensearch.sql.calcite.SysLimit; +import org.opensearch.sql.common.antlr.Parser; +import org.opensearch.sql.common.antlr.SyntaxCheckException; +import org.opensearch.sql.executor.QueryType; +import org.opensearch.sql.ppl.antlr.PPLSyntaxParser; +import org.opensearch.sql.ppl.parser.AstBuilder; +import org.opensearch.sql.ppl.parser.AstStatementBuilder; + +/** + * {@code UnifiedQueryPlanner} provides a high-level API for parsing and analyzing queries using the + * Calcite-based query engine. It serves as the primary integration point for external consumers + * such as Spark or command-line tools, abstracting away Calcite internals. + */ +public class UnifiedQueryPlanner { + /** The type of query language being used (e.g., PPL). */ + private final QueryType queryType; + + /** The parser instance responsible for converting query text into a parse tree. */ + private final Parser parser; + + /** Calcite framework configuration used during logical plan construction. */ + private final FrameworkConfig config; + + /** AST-to-RelNode visitor that builds logical plans from the parsed AST. */ + private final CalciteRelNodeVisitor relNodeVisitor = + new CalciteRelNodeVisitor(new EmptyDataSourceService()); + + /** + * Constructs a UnifiedQueryPlanner for a given query type and schema root. + * + * @param queryType the query language type (e.g., PPL) + * @param rootSchema the root Calcite schema containing all catalogs and tables + * @param defaultPath dot-separated path of schema to set as default schema + */ + public UnifiedQueryPlanner(QueryType queryType, SchemaPlus rootSchema, String defaultPath) { + this.queryType = queryType; + this.parser = buildQueryParser(queryType); + this.config = buildCalciteConfig(rootSchema, defaultPath); + } + + /** + * Parses and analyzes a query string into a Calcite logical plan (RelNode). TODO: Generate + * optimal physical plan to fully unify query execution and leverage Calcite's optimizer. + * + * @param query the raw query string in PPL or other supported syntax + * @return a logical plan representing the query + */ + public RelNode plan(String query) { + try { + return preserveCollation(analyze(parse(query))); + } catch (SyntaxCheckException e) { + // Re-throw syntax error without wrapping + throw e; + } catch (Exception e) { + throw new IllegalStateException("Failed to plan query", e); + } + } + + private Parser buildQueryParser(QueryType queryType) { + if (queryType == QueryType.PPL) { + return new PPLSyntaxParser(); + } + throw new IllegalArgumentException("Unsupported query type: " + queryType); + } + + private FrameworkConfig buildCalciteConfig(SchemaPlus rootSchema, String defaultPath) { + SchemaPlus defaultSchema = findSchemaByPath(rootSchema, defaultPath); + return Frameworks.newConfigBuilder() + .parserConfig(SqlParser.Config.DEFAULT) + .defaultSchema(defaultSchema) + .traitDefs((List) null) + .programs(Programs.calc(DefaultRelMetadataProvider.INSTANCE)) + .build(); + } + + private static SchemaPlus findSchemaByPath(SchemaPlus rootSchema, String defaultPath) { + if (defaultPath == null) { + return rootSchema; + } + + // Find schema by the path recursively + SchemaPlus current = rootSchema; + for (String part : defaultPath.split("\\.")) { + current = current.getSubSchema(part); + if (current == null) { + throw new IllegalArgumentException("Invalid default catalog path: " + defaultPath); + } + } + return current; + } + + private UnresolvedPlan parse(String query) { + ParseTree cst = parser.parse(query); + AstStatementBuilder astStmtBuilder = + new AstStatementBuilder( + new AstBuilder(query), AstStatementBuilder.StatementBuilderContext.builder().build()); + Statement statement = cst.accept(astStmtBuilder); + + if (statement instanceof Query) { + return ((Query) statement).getPlan(); + } + throw new UnsupportedOperationException( + "Only query statements are supported but got " + statement.getClass().getSimpleName()); + } + + private RelNode analyze(UnresolvedPlan ast) { + // TODO: Hardcoded query size limit (10000) for now as only logical plan is generated. + CalcitePlanContext calcitePlanContext = + CalcitePlanContext.create(config, new SysLimit(10000, 10000, 10000), queryType); + return relNodeVisitor.analyze(ast, calcitePlanContext); + } + + private RelNode preserveCollation(RelNode logical) { + RelNode calcitePlan = logical; + RelCollation collation = logical.getTraitSet().getCollation(); + if (!(logical instanceof Sort) && collation != RelCollations.EMPTY) { + calcitePlan = LogicalSort.create(logical, collation, null, null); + } + return calcitePlan; + } + + /** Builder for {@link UnifiedQueryPlanner}, supporting declarative fluent API. */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for {@link UnifiedQueryPlanner}, supporting both declarative and dynamic schema + * registration for use in query planning. + */ + public static class Builder { + private final Map catalogs = new HashMap<>(); + private String defaultNamespace; + private QueryType queryType; + private boolean cacheMetadata; + + /** + * Sets the query language frontend to be used by the planner. + * + * @param queryType the {@link QueryType}, such as PPL + * @return this builder instance + */ + public Builder language(QueryType queryType) { + this.queryType = queryType; + return this; + } + + /** + * Registers a catalog with the specified name and its associated schema. The schema can be a + * flat or nested structure (e.g., catalog → schema → table), depending on how data is + * organized. + * + * @param name the name of the catalog to register + * @param schema the schema representing the structure of the catalog + * @return this builder instance + */ + public Builder catalog(String name, Schema schema) { + catalogs.put(name, schema); + return this; + } + + /** + * Sets the default namespace path for resolving unqualified table names. + * + * @param namespace dot-separated path (e.g., "spark_catalog.default" or "opensearch") + * @return this builder instance + */ + public Builder defaultNamespace(String namespace) { + this.defaultNamespace = namespace; + return this; + } + + /** + * Enables or disables catalog metadata caching in the root schema. + * + * @param cache whether to enable metadata caching + * @return this builder instance + */ + public Builder cacheMetadata(boolean cache) { + this.cacheMetadata = cache; + return this; + } + + /** + * Builds a {@link UnifiedQueryPlanner} with the configuration. + * + * @return a new instance of {@link UnifiedQueryPlanner} + */ + public UnifiedQueryPlanner build() { + Objects.requireNonNull(queryType, "Must specify language before build"); + SchemaPlus rootSchema = CalciteSchema.createRootSchema(true, cacheMetadata).plus(); + catalogs.forEach(rootSchema::add); + return new UnifiedQueryPlanner(queryType, rootSchema, defaultNamespace); + } + } +} diff --git a/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java new file mode 100644 index 00000000000..0f7754ba501 --- /dev/null +++ b/api/src/test/java/org/opensearch/sql/api/UnifiedQueryPlannerTest.java @@ -0,0 +1,182 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +package org.opensearch.sql.api; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +import java.util.List; +import java.util.Map; +import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFactory; +import org.apache.calcite.schema.Schema; +import org.apache.calcite.schema.Table; +import org.apache.calcite.schema.impl.AbstractSchema; +import org.apache.calcite.schema.impl.AbstractTable; +import org.apache.calcite.sql.type.SqlTypeName; +import org.junit.Test; +import org.opensearch.sql.common.antlr.SyntaxCheckException; +import org.opensearch.sql.executor.QueryType; + +public class UnifiedQueryPlannerTest { + + /** Test schema consists of a test table with id and name columns */ + private final AbstractSchema testSchema = + new AbstractSchema() { + @Override + protected Map getTableMap() { + return Map.of( + "index", + new AbstractTable() { + @Override + public RelDataType getRowType(RelDataTypeFactory typeFactory) { + return typeFactory.createStructType( + List.of( + typeFactory.createSqlType(SqlTypeName.INTEGER), + typeFactory.createSqlType(SqlTypeName.VARCHAR)), + List.of("id", "name")); + } + }); + } + }; + + /** Test catalog consists of test schema above */ + private final AbstractSchema testDeepSchema = + new AbstractSchema() { + @Override + protected Map getSubSchemaMap() { + return Map.of("opensearch", testSchema); + } + }; + + @Test + public void testPPLQueryPlanning() { + UnifiedQueryPlanner planner = + UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("opensearch", testSchema) + .build(); + + RelNode plan = planner.plan("source = opensearch.index | eval f = abs(id)"); + assertNotNull("Plan should be created", plan); + } + + @Test + public void testPPLQueryPlanningWithDefaultNamespace() { + UnifiedQueryPlanner planner = + UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("opensearch", testSchema) + .defaultNamespace("opensearch") + .build(); + + assertNotNull("Plan should be created", planner.plan("source = opensearch.index")); + assertNotNull("Plan should be created", planner.plan("source = index")); + } + + @Test + public void testPPLQueryPlanningWithDefaultNamespaceMultiLevel() { + UnifiedQueryPlanner planner = + UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("catalog", testDeepSchema) + .defaultNamespace("catalog.opensearch") + .build(); + + assertNotNull("Plan should be created", planner.plan("source = catalog.opensearch.index")); + assertNotNull("Plan should be created", planner.plan("source = index")); + + // This is valid in SparkSQL, but Calcite requires "catalog" as the default root schema to + // resolve it + assertThrows(IllegalStateException.class, () -> planner.plan("source = opensearch.index")); + } + + @Test + public void testPPLQueryPlanningWithMultipleCatalogs() { + UnifiedQueryPlanner planner = + UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("catalog1", testSchema) + .catalog("catalog2", testSchema) + .build(); + + RelNode plan = + planner.plan("source = catalog1.index | lookup catalog2.index id | eval f = abs(id)"); + assertNotNull("Plan should be created with multiple catalogs", plan); + } + + @Test + public void testPPLQueryPlanningWithMultipleCatalogsAndDefaultNamespace() { + UnifiedQueryPlanner planner = + UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("catalog1", testSchema) + .catalog("catalog2", testSchema) + .defaultNamespace("catalog2") + .build(); + + RelNode plan = planner.plan("source = catalog1.index | lookup index id | eval f = abs(id)"); + assertNotNull("Plan should be created with multiple catalogs", plan); + } + + @Test + public void testPPLQueryPlanningWithMetadataCaching() { + UnifiedQueryPlanner planner = + UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("opensearch", testSchema) + .cacheMetadata(true) + .build(); + + RelNode plan = planner.plan("source = opensearch.index"); + assertNotNull("Plan should be created", plan); + } + + @Test(expected = NullPointerException.class) + public void testMissingQueryLanguage() { + UnifiedQueryPlanner.builder().catalog("opensearch", testSchema).build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testUnsupportedQueryLanguage() { + UnifiedQueryPlanner.builder() + .language(QueryType.SQL) // only PPL is supported for now + .catalog("opensearch", testSchema) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidDefaultNamespacePath() { + UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("opensearch", testSchema) + .defaultNamespace("nonexistent") // nonexistent namespace path + .build(); + } + + @Test(expected = IllegalStateException.class) + public void testUnsupportedStatementType() { + UnifiedQueryPlanner planner = + UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("opensearch", testSchema) + .build(); + + planner.plan("explain source = index"); // explain statement + } + + @Test(expected = SyntaxCheckException.class) + public void testPlanPropagatingSyntaxCheckException() { + UnifiedQueryPlanner planner = + UnifiedQueryPlanner.builder() + .language(QueryType.PPL) + .catalog("opensearch", testSchema) + .build(); + + planner.plan("source = index | eval"); // Trigger syntax error from parser + } +} diff --git a/build.gradle b/build.gradle index 19672946ebe..e38eb810ab3 100644 --- a/build.gradle +++ b/build.gradle @@ -160,6 +160,54 @@ subprojects { maven { url "https://ci.opensearch.org/ci/dbc/snapshots/lucene/" } maven { url 'https://jitpack.io' } } + + // Publish internal modules as Maven artifacts for external use, such as by opensearch-spark and opensearch-cli. + def publishedModules = ['api', 'sql', 'ppl', 'core', 'opensearch', 'common', 'protocol', 'datasources', 'legacy'] + if (publishedModules.contains(name)) { + apply plugin: 'java-library' + apply plugin: 'maven-publish' + + def fullArtifactId = "unified-query-${project.name}" + publishing { + publications { + unifiedQuery(MavenPublication) { + from components.java + groupId = "org.opensearch.query" + artifactId = fullArtifactId + + pom { + name = fullArtifactId + description = "OpenSearch unified query ${project.name} module" + licenses { + license { + name = 'The Apache License, Version 2.0' + url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + developers { + developer { + name = 'OpenSearch' + url = 'https://github.com/opensearch-project/sql' + } + } + } + } + } + + repositories { + maven { + name = "Snapshots" + url = "https://ci.opensearch.org/ci/dbc/snapshots/maven/" + url = System.getenv("MAVEN_SNAPSHOTS_S3_REPO") + credentials(AwsCredentials) { + accessKey = System.getenv("AWS_ACCESS_KEY_ID") + secretKey = System.getenv("AWS_SECRET_ACCESS_KEY") + sessionToken = System.getenv("AWS_SESSION_TOKEN") + } + } + } + } + } } // TODO: fix compiler warnings diff --git a/settings.gradle b/settings.gradle index 0e88df0b0df..4b0fa1b44f8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,6 +8,7 @@ rootProject.name = 'opensearch-sql' include 'opensearch-sql-plugin' project(':opensearch-sql-plugin').projectDir = file('plugin') +include 'api' include 'ppl' include 'common' include 'opensearch'