-
Notifications
You must be signed in to change notification settings - Fork 177
Publish internal modules separately for downstream reuse #4484
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
Changes from 9 commits
4f3cbc5
c0858b5
99724d9
413fc89
ec5e8fe
87396b7
99f181b
8c428c6
f6af494
7dc10bf
2bb0223
42b9dd5
6195a56
05dd1a4
eed569d
98e16bc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,43 @@ | ||
| name: Publish unified query modules to maven | ||
|
|
||
| on: | ||
| workflow_dispatch: | ||
| push: | ||
| branches: | ||
| - main | ||
| - 1.* | ||
| - 2.* | ||
|
|
||
| env: | ||
| SNAPSHOT_REPO_URL: https://aws.oss.sonatype.org/content/repositories/snapshots/ | ||
|
||
|
|
||
| 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 | ||
| - uses: aws-actions/[email protected] | ||
| with: | ||
| role-to-assume: ${{ secrets.PUBLISH_SNAPSHOTS_ROLE }} | ||
| aws-region: us-east-1 | ||
|
|
||
| - name: get credentials | ||
| run: | | ||
| # Get credentials for publishing | ||
| .github/get-sonatype-credentials.sh | ||
|
|
||
| - name: publish unified query modules to maven | ||
| run: | | ||
| ./gradlew publishUnifiedQueryPublicationToSnapshotsRepository | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | ||
| <dependency> | ||
| <groupId>org.opensearch.query</groupId> | ||
| <artifactId>unified-query-api</artifactId> | ||
| <version>YOUR_VERSION_HERE</version> | ||
| </dependency> | ||
| ``` | ||
|
|
||
| ### 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<String, Table> 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. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| package org.opensearch.sql.api; | ||
|
|
||
| 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; | ||
|
|
||
| import java.util.Map; | ||
| import java.util.Set; | ||
|
|
||
| /** | ||
| * A DataSourceService that assumes no access to data sources | ||
| */ | ||
| public class EmptyDataSourceService implements DataSourceService { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since CalciteRelNodeVisitor needs a data source service now, but this notion doesn't make any sense outside of a cluster, let's just fill in an empty one that always returns no results and reports any given source as nonexistent. Better solution long-term would be to make CalciteRelNodeVisitor not have this dependency to begin with, seems like it crosses boundaries. |
||
| public EmptyDataSourceService() {} | ||
|
|
||
| @Override | ||
| public DataSource getDataSource(String dataSourceName) { | ||
| return null; | ||
| } | ||
|
|
||
| @Override | ||
| public Set<DataSourceMetadata> 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<String, Object> 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; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.