├── .gitignore
├── .mvn
├── maven.config
├── wrapper
│ └── maven-wrapper.properties
├── extensions.xml
└── jvm.config
├── catalog
└── git.properties
├── Dockerfile
├── jreleaser.yml
├── .github
├── problem-matcher.json
├── dependabot.yml
└── workflows
│ ├── maven.yaml
│ └── release.yaml
├── .project
├── docker-build.sh
├── src
├── main
│ ├── resources
│ │ └── sql
│ │ │ ├── commit_stats.sql
│ │ │ └── idents.sql
│ └── java
│ │ └── pl
│ │ └── net
│ │ └── was
│ │ └── trino
│ │ └── git
│ │ ├── GitTransactionHandle.java
│ │ ├── RecordCursorProvider.java
│ │ ├── GitPlugin.java
│ │ ├── GitConfig.java
│ │ ├── GitRecordSetProvider.java
│ │ ├── GitConnectorFactory.java
│ │ ├── GitTable.java
│ │ ├── GitColumn.java
│ │ ├── GitSplit.java
│ │ ├── GitConnector.java
│ │ ├── GitColumnHandle.java
│ │ ├── GitModule.java
│ │ ├── GitTableHandle.java
│ │ ├── BranchesRecordCursor.java
│ │ ├── GitSplitManager.java
│ │ ├── ObjectsRecordCursor.java
│ │ ├── TagsRecordCursor.java
│ │ ├── GitRecordSet.java
│ │ ├── CommitsRecordCursor.java
│ │ ├── TreesRecordCursor.java
│ │ ├── GitClient.java
│ │ ├── DiffStatsRecordCursor.java
│ │ └── GitMetadata.java
└── test
│ └── java
│ └── pl
│ └── net
│ └── was
│ └── trino
│ └── git
│ ├── TestGitSplit.java
│ ├── TestGitTableHandle.java
│ ├── TestGitRecordSetProvider.java
│ ├── GitQueryRunner.java
│ ├── TestGitClient.java
│ ├── TestGitMetadata.java
│ └── TestGitRecordSet.java
├── README.md
├── pom.xml
├── LICENSE
├── mvnw
└── examples
└── achievements.sql
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 |
3 | target
4 | .idea
5 | *.iml
6 | out
7 |
--------------------------------------------------------------------------------
/.mvn/maven.config:
--------------------------------------------------------------------------------
1 | --strict-checksums
2 | -b
3 | smart
4 | -T2C
5 |
--------------------------------------------------------------------------------
/catalog/git.properties:
--------------------------------------------------------------------------------
1 | connector.name=git
2 | metadata-uri=${ENV:REPO_URL}
3 |
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | wrapperVersion=3.3.4
2 | distributionType=only-script
3 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.8/apache-maven-3.9.8-bin.zip
4 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG TRINO_VERSION
2 | FROM trinodb/trino-core:$TRINO_VERSION
3 |
4 | ARG VERSION
5 |
6 | ADD target/trino-git-$VERSION/ /usr/lib/trino/plugin/git/
7 | ADD catalog/git.properties /etc/trino/catalog/git.properties
8 |
--------------------------------------------------------------------------------
/.mvn/extensions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | io.takari.maven
5 | takari-smart-builder
6 | 1.1.0
7 |
8 |
9 |
--------------------------------------------------------------------------------
/jreleaser.yml:
--------------------------------------------------------------------------------
1 | project:
2 | name: trino-git
3 | description: This is a Trino connector to access git repos
4 | license: Apache-2
5 | java:
6 | groupId: pl.net.was
7 | version: 11
8 | authors:
9 | - Jan Waś
10 | extraProperties:
11 | inceptionYear: 2021
12 |
13 | files:
14 | artifacts:
15 | - path: "target/{{projectName}}-{{projectVersion}}.zip"
16 |
17 | release:
18 | github:
19 | owner: nineinchnick
20 |
--------------------------------------------------------------------------------
/.github/problem-matcher.json:
--------------------------------------------------------------------------------
1 | {
2 | "problemMatcher": [
3 | {
4 | "owner": "maven",
5 | "pattern": [
6 | {
7 | "regexp": "^.*\\[(ERROR|WARN(?:ING)?)\\]\\s+(.*):\\[(\\d+),(\\d+)\\] (?:error: )?[\\[\\(](.*)[\\]\\)] (.*)$",
8 | "severity": 1,
9 | "file": 2,
10 | "line": 3,
11 | "column": 4,
12 | "message": 6,
13 | "code": 5
14 | }
15 | ]
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "maven"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | groups:
8 | dependency-updates:
9 | applies-to: version-updates
10 | update-types:
11 | - major
12 | - minor
13 | - patch
14 | security-updates:
15 | applies-to: security-updates
16 | dependency-type: production
17 | - package-ecosystem: "github-actions"
18 | directory: "/"
19 | schedule:
20 | interval: "weekly"
21 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | trino-git
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.m2e.core.maven2Builder
15 |
16 |
17 |
18 |
19 |
20 | org.eclipse.jdt.core.javanature
21 | org.eclipse.m2e.core.maven2Nature
22 |
23 |
24 |
--------------------------------------------------------------------------------
/docker-build.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | set -euo pipefail
4 | set -x
5 |
6 | if [ -f release.properties ]; then
7 | VERSION=$(grep 'project.rel.pl.net.was\\:trino-git=' release.properties | cut -d'=' -f2)
8 | else
9 | VERSION=$(mvn help:evaluate -Dexpression=project.version -q -DforceStdout)
10 | fi
11 | TRINO_VERSION=$(mvn help:evaluate -Dexpression=dep.trino.version -q -DforceStdout)
12 | TAG=nineinchnick/trino-git:$VERSION
13 |
14 | docker buildx build \
15 | --platform linux/amd64,linux/arm64 \
16 | -t "$TAG" \
17 | --build-arg TRINO_VERSION="$TRINO_VERSION" \
18 | --build-arg VERSION="$VERSION" \
19 | --push \
20 | .
21 |
--------------------------------------------------------------------------------
/.mvn/jvm.config:
--------------------------------------------------------------------------------
1 | -Xmx8192m
2 | --add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
3 | --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
4 | --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
5 | --add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
6 | --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
7 | --add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
8 | --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
9 | --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
10 | --add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
11 | --add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
12 | --enable-native-access=ALL-UNNAMED
13 |
--------------------------------------------------------------------------------
/src/main/resources/sql/commit_stats.sql:
--------------------------------------------------------------------------------
1 | SELECT
2 | c.object_id,
3 | c.author_name,
4 | c.author_email,
5 | c.committer_name,
6 | c.committer_email,
7 | c.message,
8 | c.parents,
9 | c.tree_id,
10 | c.commit_time,
11 | sum(s.added_lines) AS added_lines,
12 | sum(s.deleted_lines) AS deleted_lines,
13 | count(s.commit_id) AS changed_files,
14 | avg(s.similarity_score) AS similarity_score,
15 | array_agg(s.change_type) AS change_types
16 | FROM
17 | commits c
18 | JOIN diff_stats s ON
19 | s.commit_id = c.object_id
20 | GROUP BY
21 | c.object_id,
22 | c.author_email,
23 | c.author_name,
24 | c.committer_email,
25 | c.committer_name,
26 | c.message,
27 | c.parents,
28 | c.tree_id,
29 | c.commit_time
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitTransactionHandle.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.trino.spi.connector.ConnectorTransactionHandle;
17 |
18 | public enum GitTransactionHandle
19 | implements ConnectorTransactionHandle
20 | {
21 | INSTANCE
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/RecordCursorProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.trino.spi.connector.RecordCursor;
17 | import org.eclipse.jgit.api.Git;
18 |
19 | import java.util.List;
20 | import java.util.Optional;
21 |
22 | public interface RecordCursorProvider
23 | {
24 | RecordCursor create(List columnHandles, Git repo, Optional> commitIds);
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitPlugin.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.trino.spi.Plugin;
17 | import io.trino.spi.connector.ConnectorFactory;
18 |
19 | import java.util.List;
20 |
21 | public class GitPlugin
22 | implements Plugin
23 | {
24 | @Override
25 | public Iterable getConnectorFactories()
26 | {
27 | return List.of(new GitConnectorFactory());
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.airlift.configuration.Config;
17 | import jakarta.validation.constraints.NotNull;
18 |
19 | import java.net.URI;
20 |
21 | public class GitConfig
22 | {
23 | private URI uri;
24 |
25 | @NotNull
26 | public URI getUri()
27 | {
28 | return uri;
29 | }
30 |
31 | @Config("metadata-uri")
32 | public GitConfig setUri(URI uri)
33 | {
34 | this.uri = uri;
35 | return this;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.github/workflows/maven.yaml:
--------------------------------------------------------------------------------
1 | # This workflow will build a Java project with Maven
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven
3 |
4 | name: Java CI with Maven
5 |
6 | on:
7 | pull_request:
8 | branches: [main]
9 |
10 | jobs:
11 | build:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v6
15 | - name: Set up the JDK
16 | uses: actions/setup-java@v5
17 | with:
18 | java-version: '25'
19 | distribution: 'temurin'
20 | server-id: github
21 | cache: 'maven'
22 | - name: Configure Problem Matchers
23 | run: |
24 | echo "::add-matcher::.github/problem-matcher.json"
25 | echo "::remove-matcher owner=java::"
26 |
27 | - name: Build with Maven
28 | run: ./mvnw -B package
29 |
30 | - name: Annotate run
31 | uses: trinodb/github-actions/action-surefire-report@b63800bedfbc7ab1ff2e5fe7eaecf5ab82ce6a70
32 | if: always()
33 | with:
34 | fail_if_no_tests: false
35 | skip_publishing: true
36 |
--------------------------------------------------------------------------------
/src/main/resources/sql/idents.sql:
--------------------------------------------------------------------------------
1 | -- This can produce too many stages, see the queries in the examples dir
2 | -- on how to break it down using temporary tables
3 | WITH RECURSIVE
4 | nodes (email, name) AS (
5 | SELECT DISTINCT author_email, author_name
6 | FROM commits
7 | UNION
8 | SELECT DISTINCT committer_email, committer_name
9 | FROM commits
10 | ),
11 | edges (name1, name2) AS (
12 | SELECT n1.name, n2.name
13 | FROM nodes n1
14 | INNER JOIN nodes n2 USING (email)
15 | ),
16 | walk (name1, name2, visited) AS (
17 | SELECT name1, name2, ARRAY[name1]
18 | FROM edges
19 | WHERE name1 = name2
20 | UNION ALL
21 | SELECT w.name1, e.name2, w.visited || e.name2
22 | FROM walk w
23 | INNER JOIN edges e ON e.name1 = w.name2
24 | WHERE NOT contains(w.visited, e.name2)
25 | ),
26 | result (name1, name2s) AS (
27 | SELECT name1, array_agg(DISTINCT name2 ORDER BY name2)
28 | FROM walk
29 | GROUP BY name1
30 | ),
31 | grouped (names, emails) AS (
32 | SELECT
33 | array_agg(DISTINCT n.name ORDER BY n.name) AS names,
34 | array_agg(DISTINCT n.email ORDER BY n.email) AS emails
35 | FROM result r
36 | INNER JOIN nodes n ON n.name = r.name1
37 | GROUP BY r.name2s;
38 | )
39 | SELECT
40 | emails[1] AS email,
41 | names[1] AS name,
42 | slice(emails, 2, cardinality(emails)) AS extra_emails,
43 | slice(names, 2, cardinality(emails)) AS extra_names
44 | FROM grouped
45 | ORDER BY name, names
46 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitRecordSetProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.google.common.collect.ImmutableList;
17 | import io.trino.spi.connector.ColumnHandle;
18 | import io.trino.spi.connector.ConnectorRecordSetProvider;
19 | import io.trino.spi.connector.ConnectorSession;
20 | import io.trino.spi.connector.ConnectorSplit;
21 | import io.trino.spi.connector.ConnectorTableHandle;
22 | import io.trino.spi.connector.ConnectorTransactionHandle;
23 | import io.trino.spi.connector.RecordSet;
24 |
25 | import java.util.List;
26 |
27 | public class GitRecordSetProvider
28 | implements ConnectorRecordSetProvider
29 | {
30 | @Override
31 | public RecordSet getRecordSet(
32 | ConnectorTransactionHandle transaction,
33 | ConnectorSession session,
34 | ConnectorSplit split,
35 | ConnectorTableHandle table,
36 | List extends ColumnHandle> columns)
37 | {
38 | GitSplit gitSplit = (GitSplit) split;
39 |
40 | ImmutableList.Builder handles = ImmutableList.builder();
41 | for (ColumnHandle handle : columns) {
42 | handles.add((GitColumnHandle) handle);
43 | }
44 |
45 | return new GitRecordSet(gitSplit, (GitTableHandle) table, handles.build());
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/test/java/pl/net/was/trino/git/TestGitSplit.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.airlift.json.JsonCodec;
17 | import org.junit.jupiter.api.Test;
18 |
19 | import java.net.URI;
20 | import java.net.URISyntaxException;
21 | import java.util.Optional;
22 |
23 | import static io.airlift.json.JsonCodec.jsonCodec;
24 | import static org.assertj.core.api.Assertions.assertThat;
25 |
26 | public class TestGitSplit
27 | {
28 | private final GitSplit split = new GitSplit("tableName", new URI("url.invalid"), Optional.empty());
29 |
30 | public TestGitSplit()
31 | throws URISyntaxException
32 | {}
33 |
34 | @Test
35 | public void testAddresses()
36 | throws URISyntaxException
37 | {
38 | URI testURI = new URI("url.invalid");
39 | GitSplit httpSplit = new GitSplit("tableName", testURI, Optional.empty());
40 | assertThat(httpSplit.isRemotelyAccessible()).isTrue();
41 | }
42 |
43 | @Test
44 | public void testJsonRoundTrip()
45 | {
46 | JsonCodec codec = jsonCodec(GitSplit.class);
47 | String json = codec.toJson(split);
48 | GitSplit copy = codec.fromJson(json);
49 | assertThat(copy.getTableName()).isEqualTo(split.getTableName());
50 |
51 | assertThat(copy.isRemotelyAccessible()).isTrue();
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitConnectorFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.google.inject.Injector;
17 | import io.airlift.bootstrap.Bootstrap;
18 | import io.airlift.json.JsonModule;
19 | import io.trino.spi.connector.Connector;
20 | import io.trino.spi.connector.ConnectorContext;
21 | import io.trino.spi.connector.ConnectorFactory;
22 |
23 | import java.util.Map;
24 |
25 | import static com.google.common.base.Throwables.throwIfUnchecked;
26 | import static java.util.Objects.requireNonNull;
27 |
28 | public class GitConnectorFactory
29 | implements ConnectorFactory
30 | {
31 | @Override
32 | public String getName()
33 | {
34 | return "git";
35 | }
36 |
37 | @Override
38 | public Connector create(String catalogName, Map requiredConfig, ConnectorContext context)
39 | {
40 | requireNonNull(requiredConfig, "requiredConfig is null");
41 | try {
42 | // A plugin is not required to use Guice; it is just very convenient
43 | Bootstrap app = new Bootstrap(
44 | new JsonModule(),
45 | new GitModule(catalogName, context.getTypeManager()));
46 |
47 | Injector injector = app
48 | .doNotInitializeLogging()
49 | .setRequiredConfigurationProperties(requiredConfig)
50 | .initialize();
51 | return injector.getInstance(GitConnector.class);
52 | }
53 | catch (Exception e) {
54 | throwIfUnchecked(e);
55 | throw new RuntimeException(e);
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/.github/workflows/release.yaml:
--------------------------------------------------------------------------------
1 | name: Release with Maven
2 |
3 | on:
4 | push:
5 | branches: [main]
6 |
7 | jobs:
8 | release:
9 | runs-on: ubuntu-latest
10 | if: "!contains(github.event.head_commit.message, '[ci skip]')"
11 | permissions:
12 | contents: write
13 | packages: write
14 | steps:
15 | - uses: actions/checkout@v6
16 | with:
17 | fetch-depth: 0
18 | - uses: actions/setup-java@v5
19 | with:
20 | java-version: '25'
21 | distribution: 'temurin'
22 | cache: 'maven'
23 | - name: Configure Problem Matchers
24 | run: |
25 | echo "::add-matcher::.github/problem-matcher.json"
26 | echo "::remove-matcher owner=java::"
27 | - name: Configure Git user
28 | run: |
29 | git config user.name "${{ github.event.head_commit.committer.name }}"
30 | git config user.email "${{ github.event.head_commit.committer.email }}"
31 |
32 | - name: Set up Docker Buildx
33 | uses: docker/setup-buildx-action@v3
34 | - name: Login to Docker Hub
35 | uses: docker/login-action@v3
36 | with:
37 | username: ${{ secrets.DOCKERHUB_USERNAME }}
38 | password: ${{ secrets.DOCKERHUB_TOKEN }}
39 |
40 | - name: Prepare release
41 | run: ./mvnw -B release:prepare
42 |
43 | - name: Build and push Docker image
44 | run: |
45 | ./docker-build.sh
46 |
47 | - name: Save version number in env
48 | run: |
49 | echo "VERSION=$(grep 'project.rel.pl.net.was\\:trino-git=' release.properties | cut -d'=' -f2)" >> $GITHUB_ENV
50 |
51 | - name: Publish JAR
52 | run: ./mvnw -B release:perform -Darguments="-Dgpg.skip -Dmaven.deploy.skip=true"
53 | env:
54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55 |
56 | - name: Run JReleaser
57 | uses: jreleaser/release-action@v2
58 | env:
59 | JRELEASER_PROJECT_VERSION: ${{ env.VERSION }}
60 | JRELEASER_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61 | with:
62 | setup-java: false
63 |
64 | - name: Annotate run
65 | uses: trinodb/github-actions/action-surefire-report@b63800bedfbc7ab1ff2e5fe7eaecf5ab82ce6a70
66 | if: always()
67 | with:
68 | fail_if_no_tests: false
69 | skip_publishing: true
70 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitTable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.fasterxml.jackson.annotation.JsonCreator;
17 | import com.fasterxml.jackson.annotation.JsonProperty;
18 | import com.google.common.collect.ImmutableList;
19 | import io.trino.spi.connector.ColumnMetadata;
20 |
21 | import java.util.List;
22 |
23 | import static com.google.common.base.Preconditions.checkArgument;
24 | import static com.google.common.base.Strings.isNullOrEmpty;
25 | import static java.util.Objects.requireNonNull;
26 |
27 | public class GitTable
28 | {
29 | private final String name;
30 | private final List columns;
31 | private final List columnsMetadata;
32 |
33 | @JsonCreator
34 | public GitTable(
35 | @JsonProperty("name") String name,
36 | @JsonProperty("columns") List columns)
37 | {
38 | checkArgument(!isNullOrEmpty(name), "name is null or is empty");
39 | this.name = requireNonNull(name, "name is null");
40 | this.columns = List.copyOf(requireNonNull(columns, "columns is null"));
41 |
42 | ImmutableList.Builder columnsMetadata = ImmutableList.builder();
43 | for (GitColumn column : this.columns) {
44 | columnsMetadata.add(new ColumnMetadata(column.getName(), column.getType()));
45 | }
46 | this.columnsMetadata = columnsMetadata.build();
47 | }
48 |
49 | @JsonProperty
50 | public String getName()
51 | {
52 | return name;
53 | }
54 |
55 | @JsonProperty
56 | public List getColumns()
57 | {
58 | return columns;
59 | }
60 |
61 | public List getColumnsMetadata()
62 | {
63 | return columnsMetadata;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitColumn.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.fasterxml.jackson.annotation.JsonCreator;
17 | import com.fasterxml.jackson.annotation.JsonProperty;
18 | import io.trino.spi.type.Type;
19 |
20 | import java.util.Objects;
21 |
22 | import static com.google.common.base.Preconditions.checkArgument;
23 | import static com.google.common.base.Strings.isNullOrEmpty;
24 | import static java.util.Objects.requireNonNull;
25 |
26 | public final class GitColumn
27 | {
28 | private final String name;
29 | private final Type type;
30 |
31 | @JsonCreator
32 | public GitColumn(
33 | @JsonProperty("name") String name,
34 | @JsonProperty("type") Type type)
35 | {
36 | checkArgument(!isNullOrEmpty(name), "name is null or is empty");
37 | this.name = name;
38 | this.type = requireNonNull(type, "type is null");
39 | }
40 |
41 | @JsonProperty
42 | public String getName()
43 | {
44 | return name;
45 | }
46 |
47 | @JsonProperty
48 | public Type getType()
49 | {
50 | return type;
51 | }
52 |
53 | @Override
54 | public int hashCode()
55 | {
56 | return Objects.hash(name, type);
57 | }
58 |
59 | @Override
60 | public boolean equals(Object obj)
61 | {
62 | if (this == obj) {
63 | return true;
64 | }
65 | if (obj == null || getClass() != obj.getClass()) {
66 | return false;
67 | }
68 |
69 | GitColumn other = (GitColumn) obj;
70 | return Objects.equals(this.name, other.name) &&
71 | Objects.equals(this.type, other.type);
72 | }
73 |
74 | @Override
75 | public String toString()
76 | {
77 | return name + ":" + type;
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/java/pl/net/was/trino/git/TestGitTableHandle.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.airlift.json.JsonCodec;
17 | import io.airlift.testing.EquivalenceTester;
18 | import org.junit.jupiter.api.Test;
19 |
20 | import java.util.Optional;
21 | import java.util.OptionalLong;
22 |
23 | import static io.airlift.json.JsonCodec.jsonCodec;
24 | import static org.assertj.core.api.Assertions.assertThat;
25 |
26 | public class TestGitTableHandle
27 | {
28 | private final GitTableHandle tableHandle = new GitTableHandle("schemaName", "tableName", Optional.empty(), OptionalLong.empty());
29 |
30 | @Test
31 | public void testJsonRoundTrip()
32 | {
33 | JsonCodec codec = jsonCodec(GitTableHandle.class);
34 | String json = codec.toJson(tableHandle);
35 | GitTableHandle copy = codec.fromJson(json);
36 | assertThat(copy).isEqualTo(tableHandle);
37 | }
38 |
39 | @Test
40 | public void testEquivalence()
41 | {
42 | EquivalenceTester.equivalenceTester()
43 | .addEquivalentGroup(
44 | new GitTableHandle("schema", "table", Optional.empty(), OptionalLong.empty()),
45 | new GitTableHandle("schema", "table", Optional.empty(), OptionalLong.empty()))
46 | .addEquivalentGroup(
47 | new GitTableHandle("schemaX", "table", Optional.empty(), OptionalLong.empty()),
48 | new GitTableHandle("schemaX", "table", Optional.empty(), OptionalLong.empty()))
49 | .addEquivalentGroup(
50 | new GitTableHandle("schema", "tableX", Optional.empty(), OptionalLong.empty()),
51 | new GitTableHandle("schema", "tableX", Optional.empty(), OptionalLong.empty()))
52 | .check();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitSplit.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.fasterxml.jackson.annotation.JsonCreator;
17 | import com.fasterxml.jackson.annotation.JsonProperty;
18 | import io.trino.spi.HostAddress;
19 | import io.trino.spi.connector.ConnectorSplit;
20 |
21 | import java.net.URI;
22 | import java.util.List;
23 | import java.util.Optional;
24 |
25 | import static java.util.Objects.requireNonNull;
26 |
27 | public class GitSplit
28 | implements ConnectorSplit
29 | {
30 | // split needs to track the URI from config to use it in RecordSet
31 | private final URI uri;
32 | // split needs to track for which table it was created for to use it in RecordSetProvider
33 | private final String tableName;
34 | private final Optional> commitIds;
35 |
36 | @JsonCreator
37 | public GitSplit(
38 | @JsonProperty("tableName") String tableName,
39 | @JsonProperty("uri") URI uri,
40 | @JsonProperty("commitIds") Optional> commitIds)
41 | {
42 | this.tableName = requireNonNull(tableName, "table name is null");
43 | this.uri = requireNonNull(uri, "uri is null");
44 | this.commitIds = requireNonNull(commitIds, "commitIds is null");
45 | }
46 |
47 | @JsonProperty
48 | public String getTableName()
49 | {
50 | return tableName;
51 | }
52 |
53 | @JsonProperty
54 | public URI getUri()
55 | {
56 | return uri;
57 | }
58 |
59 | @JsonProperty
60 | public Optional> getCommitIds()
61 | {
62 | return commitIds;
63 | }
64 |
65 | @Override
66 | public boolean isRemotelyAccessible()
67 | {
68 | // only http or https is remotely accessible
69 | return true;
70 | }
71 |
72 | @Override
73 | public List getAddresses()
74 | {
75 | return List.of();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitConnector.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.google.inject.Inject;
17 | import io.airlift.bootstrap.LifeCycleManager;
18 | import io.trino.spi.connector.Connector;
19 | import io.trino.spi.connector.ConnectorMetadata;
20 | import io.trino.spi.connector.ConnectorRecordSetProvider;
21 | import io.trino.spi.connector.ConnectorSession;
22 | import io.trino.spi.connector.ConnectorSplitManager;
23 | import io.trino.spi.connector.ConnectorTransactionHandle;
24 | import io.trino.spi.transaction.IsolationLevel;
25 |
26 | import static java.util.Objects.requireNonNull;
27 | import static pl.net.was.trino.git.GitTransactionHandle.INSTANCE;
28 |
29 | public class GitConnector
30 | implements Connector
31 | {
32 | private final LifeCycleManager lifeCycleManager;
33 | private final GitMetadata metadata;
34 | private final GitSplitManager splitManager;
35 | private final GitRecordSetProvider recordSetProvider;
36 |
37 | @Inject
38 | public GitConnector(
39 | LifeCycleManager lifeCycleManager,
40 | GitMetadata metadata,
41 | GitSplitManager splitManager,
42 | GitRecordSetProvider recordSetProvider)
43 | {
44 | this.lifeCycleManager = requireNonNull(lifeCycleManager, "lifeCycleManager is null");
45 | this.metadata = requireNonNull(metadata, "metadata is null");
46 | this.splitManager = requireNonNull(splitManager, "splitManager is null");
47 | this.recordSetProvider = requireNonNull(recordSetProvider, "recordSetProvider is null");
48 | }
49 |
50 | @Override
51 | public ConnectorTransactionHandle beginTransaction(IsolationLevel isolationLevel, boolean readOnly, boolean autoCommit)
52 | {
53 | return INSTANCE;
54 | }
55 |
56 | @Override
57 | public ConnectorMetadata getMetadata(ConnectorSession session, ConnectorTransactionHandle transactionHandle)
58 | {
59 | return metadata;
60 | }
61 |
62 | @Override
63 | public ConnectorSplitManager getSplitManager()
64 | {
65 | return splitManager;
66 | }
67 |
68 | @Override
69 | public ConnectorRecordSetProvider getRecordSetProvider()
70 | {
71 | return recordSetProvider;
72 | }
73 |
74 | @Override
75 | public final void shutdown()
76 | {
77 | lifeCycleManager.stop();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/java/pl/net/was/trino/git/TestGitRecordSetProvider.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.google.common.collect.ImmutableMap;
17 | import io.trino.spi.connector.RecordCursor;
18 | import io.trino.spi.connector.RecordSet;
19 | import org.eclipse.jgit.api.errors.GitAPIException;
20 | import org.junit.jupiter.api.BeforeAll;
21 | import org.junit.jupiter.api.Test;
22 |
23 | import java.io.IOException;
24 | import java.net.URI;
25 | import java.util.LinkedHashMap;
26 | import java.util.List;
27 | import java.util.Map;
28 | import java.util.Optional;
29 | import java.util.OptionalLong;
30 |
31 | import static io.trino.spi.type.VarcharType.createUnboundedVarcharType;
32 | import static io.trino.testing.TestingConnectorSession.SESSION;
33 | import static org.assertj.core.api.Assertions.assertThat;
34 |
35 | public class TestGitRecordSetProvider
36 | {
37 | private static final URI uri = URI.create("fake.invalid");
38 |
39 | @BeforeAll
40 | public static void setUp()
41 | throws IOException, GitAPIException
42 | {
43 | TestGitClient.setupRepo(uri);
44 | }
45 |
46 | @Test
47 | public void testGetRecordSet()
48 | {
49 | GitRecordSetProvider recordSetProvider = new GitRecordSetProvider();
50 | RecordSet recordSet = recordSetProvider.getRecordSet(
51 | GitTransactionHandle.INSTANCE,
52 | SESSION,
53 | new GitSplit("commits", uri, Optional.empty()),
54 | new GitTableHandle("default", "commits", Optional.empty(), OptionalLong.empty()),
55 | List.of(
56 | new GitColumnHandle("object_id", createUnboundedVarcharType(), 0),
57 | new GitColumnHandle("author_name", createUnboundedVarcharType(), 1)));
58 | assertThat(recordSet).isNotNull();
59 |
60 | RecordCursor cursor = recordSet.cursor();
61 | assertThat(cursor).isNotNull();
62 |
63 | Map data = new LinkedHashMap<>();
64 | while (cursor.advanceNextPosition()) {
65 | data.put(cursor.getSlice(0).toStringUtf8(), cursor.getSlice(1).toStringUtf8());
66 | }
67 | assertThat(data).isEqualTo(ImmutableMap.builder()
68 | .put("080dfdf0aac7d302dc31d57f62942bb6533944f7", "test")
69 | .put("c3b14e59f88d0d6597b98ee93cf61e7556d540a4", "test")
70 | .build());
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitColumnHandle.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.fasterxml.jackson.annotation.JsonCreator;
17 | import com.fasterxml.jackson.annotation.JsonProperty;
18 | import io.trino.spi.connector.ColumnHandle;
19 | import io.trino.spi.connector.ColumnMetadata;
20 | import io.trino.spi.type.Type;
21 |
22 | import static com.google.common.base.MoreObjects.toStringHelper;
23 | import static java.util.Objects.requireNonNull;
24 |
25 | public final class GitColumnHandle
26 | implements ColumnHandle
27 | {
28 | private final String columnName;
29 | private final Type columnType;
30 | private final int ordinalPosition;
31 |
32 | @JsonCreator
33 | public GitColumnHandle(
34 | @JsonProperty("columnName") String columnName,
35 | @JsonProperty("columnType") Type columnType,
36 | @JsonProperty("ordinalPosition") int ordinalPosition)
37 | {
38 | this.columnName = requireNonNull(columnName, "columnName is null");
39 | this.columnType = requireNonNull(columnType, "columnType is null");
40 | this.ordinalPosition = ordinalPosition;
41 | }
42 |
43 | @JsonProperty
44 | public String getColumnName()
45 | {
46 | return columnName;
47 | }
48 |
49 | @JsonProperty
50 | public Type getColumnType()
51 | {
52 | return columnType;
53 | }
54 |
55 | @JsonProperty
56 | public int getOrdinalPosition()
57 | {
58 | return ordinalPosition;
59 | }
60 |
61 | public ColumnMetadata getColumnMetadata()
62 | {
63 | return new ColumnMetadata(columnName, columnType);
64 | }
65 |
66 | @Override
67 | public int hashCode()
68 | {
69 | return columnName.hashCode();
70 | }
71 |
72 | @Override
73 | public boolean equals(Object obj)
74 | {
75 | if (this == obj) {
76 | return true;
77 | }
78 | if ((obj == null) || (getClass() != obj.getClass())) {
79 | return false;
80 | }
81 |
82 | GitColumnHandle other = (GitColumnHandle) obj;
83 | return columnName.equals(other.columnName);
84 | }
85 |
86 | @Override
87 | public String toString()
88 | {
89 | return toStringHelper(this)
90 | .add("columnName", columnName)
91 | .add("columnType", columnType)
92 | .add("ordinalPosition", ordinalPosition)
93 | .toString();
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitModule.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.fasterxml.jackson.databind.DeserializationContext;
17 | import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer;
18 | import com.google.inject.Binder;
19 | import com.google.inject.Inject;
20 | import com.google.inject.Module;
21 | import com.google.inject.Scopes;
22 | import io.trino.spi.type.Type;
23 | import io.trino.spi.type.TypeId;
24 | import io.trino.spi.type.TypeManager;
25 |
26 | import static io.airlift.configuration.ConfigBinder.configBinder;
27 | import static io.airlift.json.JsonBinder.jsonBinder;
28 | import static io.airlift.json.JsonCodec.listJsonCodec;
29 | import static io.airlift.json.JsonCodecBinder.jsonCodecBinder;
30 | import static java.util.Objects.requireNonNull;
31 |
32 | public class GitModule
33 | implements Module
34 | {
35 | private final String catalogName;
36 | private final TypeManager typeManager;
37 |
38 | @Inject
39 | public GitModule(String catalogName, TypeManager typeManager)
40 | {
41 | this.catalogName = requireNonNull(catalogName, "catalogName is null");
42 | this.typeManager = requireNonNull(typeManager, "typeManager is null");
43 | }
44 |
45 | @Override
46 | public void configure(Binder binder)
47 | {
48 | binder.bindConstant().annotatedWith(GitMetadata.CatalogName.class).to(catalogName);
49 | binder.bind(TypeManager.class).toInstance(typeManager);
50 |
51 | binder.bind(GitConnector.class).in(Scopes.SINGLETON);
52 | binder.bind(GitMetadata.class).in(Scopes.SINGLETON);
53 | binder.bind(GitClient.class).in(Scopes.SINGLETON);
54 | binder.bind(GitSplitManager.class).in(Scopes.SINGLETON);
55 | binder.bind(GitRecordSetProvider.class).in(Scopes.SINGLETON);
56 | configBinder(binder).bindConfig(GitConfig.class);
57 |
58 | jsonBinder(binder).addDeserializerBinding(Type.class).to(TypeDeserializer.class);
59 | jsonCodecBinder(binder).bindMapJsonCodec(String.class, listJsonCodec(GitTable.class));
60 | }
61 |
62 | public static final class TypeDeserializer
63 | extends FromStringDeserializer
64 | {
65 | private final TypeManager typeManager;
66 |
67 | @Inject
68 | public TypeDeserializer(TypeManager typeManager)
69 | {
70 | super(Type.class);
71 | this.typeManager = requireNonNull(typeManager, "typeManager is null");
72 | }
73 |
74 | @Override
75 | protected Type _deserialize(String value, DeserializationContext context)
76 | {
77 | return typeManager.getType(TypeId.of(value));
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitTableHandle.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.fasterxml.jackson.annotation.JsonCreator;
17 | import com.fasterxml.jackson.annotation.JsonProperty;
18 | import io.trino.spi.connector.ConnectorTableHandle;
19 | import io.trino.spi.connector.SchemaTableName;
20 |
21 | import java.util.List;
22 | import java.util.Objects;
23 | import java.util.Optional;
24 | import java.util.OptionalLong;
25 |
26 | import static java.util.Objects.requireNonNull;
27 |
28 | public final class GitTableHandle
29 | implements ConnectorTableHandle
30 | {
31 | private final String schemaName;
32 | private final String tableName;
33 | private final Optional> commitIds;
34 | private final OptionalLong limit;
35 |
36 | @JsonCreator
37 | public GitTableHandle(
38 | @JsonProperty("schemaName") String schemaName,
39 | @JsonProperty("tableName") String tableName,
40 | @JsonProperty("commitIds") Optional> commitIds,
41 | @JsonProperty("limit") OptionalLong limit)
42 | {
43 | this.schemaName = requireNonNull(schemaName, "schemaName is null");
44 | this.tableName = requireNonNull(tableName, "tableName is null");
45 | this.commitIds = requireNonNull(commitIds, "commitIds is null");
46 | this.limit = requireNonNull(limit, "limit is null");
47 | }
48 |
49 | @JsonProperty
50 | public String getSchemaName()
51 | {
52 | return schemaName;
53 | }
54 |
55 | @JsonProperty
56 | public String getTableName()
57 | {
58 | return tableName;
59 | }
60 |
61 | public SchemaTableName toSchemaTableName()
62 | {
63 | return new SchemaTableName(schemaName, tableName);
64 | }
65 |
66 | @JsonProperty
67 | public Optional> getCommitIds()
68 | {
69 | return commitIds;
70 | }
71 |
72 | @JsonProperty
73 | public OptionalLong getLimit()
74 | {
75 | return limit;
76 | }
77 |
78 | @Override
79 | public int hashCode()
80 | {
81 | return Objects.hash(schemaName, tableName);
82 | }
83 |
84 | @Override
85 | public boolean equals(Object obj)
86 | {
87 | if (this == obj) {
88 | return true;
89 | }
90 | if ((obj == null) || (getClass() != obj.getClass())) {
91 | return false;
92 | }
93 |
94 | GitTableHandle other = (GitTableHandle) obj;
95 | return Objects.equals(this.schemaName, other.schemaName) &&
96 | Objects.equals(this.tableName, other.tableName) &&
97 | Objects.equals(this.commitIds, other.commitIds) &&
98 | Objects.equals(this.limit, other.limit);
99 | }
100 |
101 | @Override
102 | public String toString()
103 | {
104 | return schemaName + ":" + tableName;
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/test/java/pl/net/was/trino/git/GitQueryRunner.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.airlift.log.Level;
17 | import io.airlift.log.Logger;
18 | import io.airlift.log.Logging;
19 | import io.trino.Session;
20 | import io.trino.plugin.memory.MemoryPlugin;
21 | import io.trino.plugin.tpch.TpchPlugin;
22 | import io.trino.testing.DistributedQueryRunner;
23 |
24 | import java.util.HashMap;
25 | import java.util.Map;
26 |
27 | import static io.airlift.testing.Closeables.closeAllSuppress;
28 | import static io.trino.testing.TestingSession.testSessionBuilder;
29 | import static java.util.Objects.requireNonNullElse;
30 |
31 | public final class GitQueryRunner
32 | {
33 | private GitQueryRunner() {}
34 |
35 | private static final String TPCH_SCHEMA = "tpch";
36 |
37 | public static DistributedQueryRunner createGitQueryRunner(
38 | Map extraProperties,
39 | Map connectorProperties)
40 | throws Exception
41 | {
42 | DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(createSession())
43 | .setExtraProperties(extraProperties)
44 | .build();
45 | try {
46 | queryRunner.installPlugin(new MemoryPlugin());
47 | queryRunner.createCatalog("memory", "memory");
48 |
49 | queryRunner.installPlugin(new TpchPlugin());
50 | queryRunner.createCatalog("tpch", "tpch");
51 |
52 | connectorProperties = new HashMap<>(Map.copyOf(connectorProperties));
53 |
54 | queryRunner.installPlugin(new GitPlugin());
55 | queryRunner.createCatalog("git", "git", connectorProperties);
56 |
57 | return queryRunner;
58 | }
59 | catch (Throwable e) {
60 | closeAllSuppress(e, queryRunner);
61 | throw e;
62 | }
63 | }
64 |
65 | private static Session createSession()
66 | {
67 | return testSessionBuilder()
68 | .setCatalog("git")
69 | .setSchema(TPCH_SCHEMA)
70 | .build();
71 | }
72 |
73 | public static void main(String[] args)
74 | throws Exception
75 | {
76 | Logging logging = Logging.initialize();
77 | logging.setLevel("io.trino.plugin", Level.DEBUG);
78 | logging.setLevel("io.trino.spi", Level.DEBUG);
79 | logging.setLevel("pl.net.was", Level.DEBUG);
80 |
81 | String url = "https://github.com/nineinchnick/trino-git.git";
82 | if (args.length > 0) {
83 | url = args[0];
84 | }
85 |
86 | DistributedQueryRunner queryRunner = createGitQueryRunner(
87 | Map.of("http-server.http.port", requireNonNullElse(System.getenv("TRINO_PORT"), "8081")),
88 | Map.of("metadata-uri", url));
89 |
90 | Logger log = Logger.get(GitQueryRunner.class);
91 | log.info("======== SERVER STARTED ========");
92 | log.info("\n====\n%s\n====", queryRunner.getCoordinator().getBaseUrl());
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/test/java/pl/net/was/trino/git/TestGitClient.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import org.eclipse.jgit.api.Git;
17 | import org.eclipse.jgit.api.errors.GitAPIException;
18 | import org.eclipse.jgit.errors.ConfigInvalidException;
19 | import org.eclipse.jgit.lib.PersonIdent;
20 | import org.eclipse.jgit.lib.Repository;
21 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
22 | import org.eclipse.jgit.util.SystemReader;
23 | import org.junit.jupiter.api.Test;
24 |
25 | import java.io.File;
26 | import java.io.IOException;
27 | import java.io.PrintWriter;
28 | import java.net.URI;
29 | import java.time.Instant;
30 | import java.util.Set;
31 |
32 | import static com.google.common.io.MoreFiles.deleteRecursively;
33 | import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;
34 | import static java.time.ZoneOffset.UTC;
35 | import static org.assertj.core.api.Assertions.assertThat;
36 |
37 | public class TestGitClient
38 | {
39 | @Test
40 | public void testMetadata()
41 | {
42 | GitClient client = new GitClient(new GitConfig());
43 | assertThat(client.getSchemaNames()).isEqualTo(Set.of("default"));
44 | }
45 |
46 | public static void setupRepo(URI uri)
47 | throws IOException, GitAPIException
48 | {
49 | // make sure the global Git config is not being used
50 | try {
51 | SystemReader.getInstance().getUserConfig().clear();
52 | }
53 | catch (ConfigInvalidException e) {
54 | // ignore
55 | }
56 |
57 | // ensure the repo dir exists, remove and recreate if necessary
58 | File localPath;
59 | try {
60 | localPath = GitRecordSet.ensureDir(uri.toString());
61 | }
62 | catch (IOException ignored) {
63 | return;
64 | }
65 | if (localPath.exists()) {
66 | deleteRecursively(localPath.toPath(), ALLOW_INSECURE);
67 | }
68 |
69 | Repository repository = FileRepositoryBuilder.create(new File(localPath, ".git"));
70 | repository.create();
71 |
72 | // create a new file
73 | File myFile = new File(repository.getDirectory().getParent(), "testfile");
74 | if (!myFile.createNewFile()) {
75 | throw new IOException("Could not create file " + myFile);
76 | }
77 |
78 | PersonIdent author = new PersonIdent("test", "test@invalid.com", Instant.ofEpochSecond(1580897313L), UTC);
79 | // commit the new file
80 | Git git = new Git(repository);
81 | git.add().addFilepattern(".").call();
82 | git.commit()
83 | .setMessage("Commit all changes including additions")
84 | .setAuthor(author)
85 | .setCommitter(author)
86 | .call();
87 |
88 | try (PrintWriter writer = new PrintWriter(myFile)) {
89 | writer.append("Hello, world!");
90 | }
91 | if (!myFile.setLastModified(1580897600000L)) {
92 | throw new IOException("Could not set last modified on file " + myFile);
93 | }
94 |
95 | // Stage all changed files, omitting new files, and commit with one command
96 | git.commit()
97 | .setAll(true)
98 | .setMessage("Commit changes to all files")
99 | .setAuthor(author)
100 | .setCommitter(author)
101 | .call();
102 |
103 | git.tag()
104 | .setName("tag_for_testing")
105 | .setTagger(author)
106 | .call();
107 |
108 | git.tag()
109 | .setName("unannotated_tag_for_testing")
110 | .setAnnotated(false)
111 | .call();
112 |
113 | // ensure all loose objects are packed
114 | git.gc().call();
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Trino git Connector
2 | ===================
3 |
4 | [](https://github.com/nineinchnick/trino-git/actions/workflows/release.yaml)
5 |
6 | This is a [Trino](http://trino.io/) connector to access git repos. Please keep in mind that this is not production ready and it was created for tests.
7 |
8 | # Quick Start
9 |
10 | To run a Docker container with the connector, run the following:
11 | ```bash
12 | docker run \
13 | -d \
14 | --name trino-git \
15 | -e REPO_URL=https://github.com/nineinchnick/trino-rest.git \
16 | -p 8080:8080 \
17 | nineinchnick/trino-git:0.87
18 | ```
19 |
20 | Then use your favourite SQL client to connect to Trino running at http://localhost:8080
21 |
22 | # Usage
23 |
24 | Download one of the ZIP packages, unzip it and copy the `trino-git-0.87` directory to the plugin directory on every node in your Trino cluster.
25 | Create a `github.properties` file in your Trino catalog directory and point to a remote repo.
26 | You can also use a path to a local repo if it's available on every worker node.
27 |
28 | ```
29 | connector.name=git
30 | metadata-uri=https://github.com/nineinchnick/trino-git.git
31 | ```
32 |
33 | After reloading Trino, you should be able to connect to the `git` catalog and see the following tables in the `default` schema:
34 | * `branches`
35 | * `commits` - all commits from every branch, with author, committer, message and commit time
36 | * `diff_stats` - any files modified, deleted or renamed in every commit, with number of added and/or deleted lines
37 | * `objects` - every file contents
38 | * `tags`
39 | * `trees` - all files in every commit, with file mode and attributes
40 |
41 | To see who has commits with only deleted lines:
42 |
43 | ```sql
44 | SELECT
45 | i.name,
46 | i.email,
47 | min(c.commit_time) FILTER (WHERE c.added_lines = 0 AND c.deleted_lines != 0) AS first_delete_only_commit_at,
48 | count(*) FILTER (WHERE c.added_lines = 0 AND c.deleted_lines != 0) AS delete_only_commit_count,
49 | CAST(count(*) FILTER (WHERE c.added_lines = 0 AND c.deleted_lines != 0) AS double) / CAST(COUNT(*) AS double) AS delete_only_commit_ratio
50 | FROM
51 | commit_stats c
52 | JOIN idents i ON
53 | c.author_email = i.email OR CONTAINS(i.extra_emails, c.author_email)
54 | GROUP BY
55 | i.name,
56 | i.email
57 | HAVING
58 | count(*) FILTER (WHERE c.added_lines = 0 AND c.deleted_lines != 0) != 0
59 | ORDER BY
60 | i.name,
61 | i.email;
62 | ```
63 |
64 | Should return:
65 | ```
66 | name |email |first_delete_only_commit_at|delete_only_commit_count|delete_only_commit_ratio|
67 | -------|--------------|---------------------------|------------------------|------------------------|
68 | Jan Was|jan@was.net.pl| 2021-01-09 23:22:28| 2| 0.08695652173913043|
69 | ```
70 |
71 | # Build
72 |
73 | Run all the unit test classes.
74 | ```
75 | mvn test
76 | ```
77 |
78 | Creates a deployable jar file
79 | ```
80 | mvn clean compile package
81 | ```
82 |
83 | Copy jar files in target directory to use git connector in your Trino cluster.
84 | ```
85 | cp -p target/*.jar ${PLUGIN_DIRECTORY}/git/
86 | ```
87 |
88 | # Deploy
89 |
90 | An example command to run the Trino server with the git plugin and catalog enabled:
91 |
92 | ```bash
93 | src=$(git rev-parse --show-toplevel)
94 | docker run \
95 | -v $src/target/trino-git-0.70-SNAPSHOT:/usr/lib/trino/plugin/git \
96 | -v $src/catalog:/usr/lib/trino/default/etc/catalog \
97 | -p 8080:8080 \
98 | --name trino \
99 | -d \
100 | trinodb/trino:462
101 | ```
102 |
103 | Connect to that server using:
104 | ```bash
105 | docker run -it --rm --link trino trinodb/trino:462 trino --server trino:8080 --catalog git --schema default
106 | ```
107 |
108 | # References
109 |
110 | If you're looking to analize the structure or contents of a Git repo, [gitbase](https://github.com/src-d/gitbase) could be more suitable for such task.
111 | It could even work with Trino, since Trino has a [MySQL connector](https://trino.io/docs/current/connector/mysql.html).
112 |
113 | If you also want to analyze Github issues, pull requests (with review comments) or workflow runs and jobs,
114 | check out the Github connector in [trino-rest](https://github.com/nineinchnick/trino-rest).
115 |
116 | This effort is inspired by [Acha](https://github.com/someteam/acha), to be able to calculate achievements based on contents of a Git repository using SQL.
117 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/BranchesRecordCursor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.airlift.slice.Slice;
17 | import io.airlift.slice.Slices;
18 | import io.trino.spi.connector.RecordCursor;
19 | import io.trino.spi.type.Type;
20 | import org.eclipse.jgit.api.Git;
21 | import org.eclipse.jgit.api.ListBranchCommand;
22 | import org.eclipse.jgit.api.errors.GitAPIException;
23 | import org.eclipse.jgit.lib.Ref;
24 | import org.eclipse.jgit.revwalk.RevCommit;
25 | import org.eclipse.jgit.revwalk.RevWalk;
26 |
27 | import java.io.IOException;
28 | import java.util.Iterator;
29 | import java.util.List;
30 |
31 | import static com.google.common.base.Preconditions.checkArgument;
32 | import static com.google.common.base.Preconditions.checkState;
33 |
34 | public class BranchesRecordCursor
35 | implements RecordCursor
36 | {
37 | private final List columnHandles;
38 | private final int[] fieldToColumnIndex;
39 |
40 | private final RevWalk walk;
41 | private RevCommit main;
42 |
43 | private Iterator[ branches;
44 | private Ref branch;
45 |
46 | private List fields;
47 |
48 | public BranchesRecordCursor(List columnHandles, Git repo)
49 | {
50 | this.columnHandles = columnHandles;
51 |
52 | fieldToColumnIndex = new int[columnHandles.size()];
53 | for (int i = 0; i < columnHandles.size(); i++) {
54 | fieldToColumnIndex[i] = columnHandles.get(i).getOrdinalPosition();
55 | }
56 |
57 | try {
58 | branches = repo.branchList().setListMode(ListBranchCommand.ListMode.ALL).call().iterator();
59 | }
60 | catch (GitAPIException ignored) {
61 | //pass
62 | }
63 |
64 | walk = new RevWalk(repo.getRepository());
65 | try {
66 | Ref head = repo.getRepository().findRef("HEAD");
67 | main = walk.parseCommit(head.getObjectId());
68 | }
69 | catch (IOException ignored) {
70 | // pass
71 | }
72 | }
73 |
74 | @Override
75 | public long getCompletedBytes()
76 | {
77 | return 0;
78 | }
79 |
80 | @Override
81 | public long getReadTimeNanos()
82 | {
83 | return 0;
84 | }
85 |
86 | @Override
87 | public Type getType(int field)
88 | {
89 | checkArgument(field < columnHandles.size(), "Invalid field index");
90 | return columnHandles.get(field).getColumnType();
91 | }
92 |
93 | @Override
94 | public boolean advanceNextPosition()
95 | {
96 | if (!branches.hasNext()) {
97 | return false;
98 | }
99 | branch = branches.next();
100 |
101 | fields = List.of(
102 | branch.getObjectId().getName(),
103 | branch.getName());
104 |
105 | return true;
106 | }
107 |
108 | private String getFieldValue(int field)
109 | {
110 | checkState(fields != null, "Cursor has not been advanced yet");
111 |
112 | int columnIndex = fieldToColumnIndex[field];
113 | return fields.get(columnIndex);
114 | }
115 |
116 | @Override
117 | public boolean getBoolean(int field)
118 | {
119 | try {
120 | RevCommit current = walk.parseCommit(branch.getObjectId());
121 | return walk.isMergedInto(current, main);
122 | }
123 | catch (IOException e) {
124 | return false;
125 | }
126 | }
127 |
128 | @Override
129 | public long getLong(int field)
130 | {
131 | throw new UnsupportedOperationException();
132 | }
133 |
134 | @Override
135 | public double getDouble(int field)
136 | {
137 | throw new UnsupportedOperationException();
138 | }
139 |
140 | @Override
141 | public Slice getSlice(int field)
142 | {
143 | return Slices.utf8Slice(getFieldValue(field));
144 | }
145 |
146 | @Override
147 | public Object getObject(int field)
148 | {
149 | throw new UnsupportedOperationException();
150 | }
151 |
152 | @Override
153 | public boolean isNull(int field)
154 | {
155 | return false;
156 | }
157 |
158 | @Override
159 | public void close()
160 | {
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/GitSplitManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import com.google.inject.Inject;
17 | import io.trino.spi.connector.ColumnHandle;
18 | import io.trino.spi.connector.ConnectorSession;
19 | import io.trino.spi.connector.ConnectorSplitManager;
20 | import io.trino.spi.connector.ConnectorSplitSource;
21 | import io.trino.spi.connector.ConnectorTableHandle;
22 | import io.trino.spi.connector.ConnectorTransactionHandle;
23 | import io.trino.spi.connector.Constraint;
24 | import io.trino.spi.connector.DynamicFilter;
25 | import io.trino.spi.connector.FixedSplitSource;
26 | import io.trino.spi.predicate.TupleDomain;
27 |
28 | import java.util.List;
29 | import java.util.concurrent.CompletableFuture;
30 | import java.util.concurrent.ExecutionException;
31 |
32 | import static io.trino.spi.connector.DynamicFilter.NOT_BLOCKED;
33 | import static java.util.Objects.requireNonNull;
34 | import static java.util.concurrent.TimeUnit.MILLISECONDS;
35 | import static pl.net.was.trino.git.GitMetadata.getCommitIds;
36 |
37 | public class GitSplitManager
38 | implements ConnectorSplitManager
39 | {
40 | private final GitConfig config;
41 |
42 | @Inject
43 | public GitSplitManager(GitConfig config)
44 | {
45 | this.config = requireNonNull(config, "config is null");
46 | }
47 |
48 | @Override
49 | public ConnectorSplitSource getSplits(
50 | ConnectorTransactionHandle transaction,
51 | ConnectorSession session,
52 | ConnectorTableHandle connectorTableHandle,
53 | DynamicFilter dynamicFilter,
54 | Constraint constraint)
55 | {
56 | long timeoutMillis = 20000;
57 | if (!dynamicFilter.isAwaitable()) {
58 | return getSplitSource(connectorTableHandle, dynamicFilter);
59 | }
60 | CompletableFuture> dynamicFilterFuture = whenCompleted(dynamicFilter)
61 | .completeOnTimeout(null, timeoutMillis, MILLISECONDS);
62 | CompletableFuture splitSourceFuture = dynamicFilterFuture.thenApply(
63 | ignored -> getSplitSource(connectorTableHandle, dynamicFilter));
64 | return new GitDynamicFilteringSplitSource(dynamicFilterFuture, splitSourceFuture);
65 | }
66 |
67 | private ConnectorSplitSource getSplitSource(
68 | ConnectorTableHandle table,
69 | DynamicFilter dynamicFilter)
70 | {
71 | GitTableHandle handle = (GitTableHandle) table;
72 |
73 | TupleDomain constraint = dynamicFilter.getCurrentPredicate().simplify(100);
74 |
75 | List splits = List.of(new GitSplit(handle.getTableName(), config.getUri(), getCommitIds(constraint)));
76 |
77 | return new FixedSplitSource(splits);
78 | }
79 |
80 | private static CompletableFuture> whenCompleted(DynamicFilter dynamicFilter)
81 | {
82 | if (dynamicFilter.isAwaitable()) {
83 | return dynamicFilter.isBlocked().thenCompose(ignored -> whenCompleted(dynamicFilter));
84 | }
85 | return NOT_BLOCKED;
86 | }
87 |
88 | private static class GitDynamicFilteringSplitSource
89 | implements ConnectorSplitSource
90 | {
91 | private final CompletableFuture> dynamicFilterFuture;
92 | private final CompletableFuture splitSourceFuture;
93 |
94 | private GitDynamicFilteringSplitSource(
95 | CompletableFuture> dynamicFilterFuture,
96 | CompletableFuture splitSourceFuture)
97 | {
98 | this.dynamicFilterFuture = requireNonNull(dynamicFilterFuture, "dynamicFilterFuture is null");
99 | this.splitSourceFuture = requireNonNull(splitSourceFuture, "splitSourceFuture is null");
100 | }
101 |
102 | @Override
103 | public CompletableFuture getNextBatch(int maxSize)
104 | {
105 | return splitSourceFuture.thenCompose(splitSource -> splitSource.getNextBatch(maxSize));
106 | }
107 |
108 | @Override
109 | public void close()
110 | {
111 | if (!dynamicFilterFuture.cancel(true)) {
112 | splitSourceFuture.thenAccept(ConnectorSplitSource::close);
113 | }
114 | }
115 |
116 | @Override
117 | public boolean isFinished()
118 | {
119 | if (!splitSourceFuture.isDone()) {
120 | return false;
121 | }
122 | if (splitSourceFuture.isCompletedExceptionally()) {
123 | return false;
124 | }
125 | try {
126 | return splitSourceFuture.get().isFinished();
127 | }
128 | catch (InterruptedException | ExecutionException e) {
129 | throw new RuntimeException(e);
130 | }
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/ObjectsRecordCursor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.airlift.slice.Slice;
17 | import io.airlift.slice.Slices;
18 | import io.trino.spi.connector.RecordCursor;
19 | import io.trino.spi.type.Type;
20 | import org.eclipse.jgit.api.Git;
21 | import org.eclipse.jgit.internal.storage.file.FileRepository;
22 | import org.eclipse.jgit.internal.storage.file.Pack;
23 | import org.eclipse.jgit.internal.storage.file.PackIndex;
24 | import org.eclipse.jgit.lib.ObjectId;
25 | import org.eclipse.jgit.lib.ObjectLoader;
26 |
27 | import java.io.IOException;
28 | import java.util.Collection;
29 | import java.util.HashMap;
30 | import java.util.Iterator;
31 | import java.util.List;
32 | import java.util.Map;
33 | import java.util.function.Function;
34 |
35 | import static com.google.common.base.Preconditions.checkArgument;
36 | import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
37 |
38 | public class ObjectsRecordCursor
39 | implements RecordCursor
40 | {
41 | private final List columnHandles;
42 |
43 | private final FileRepository fileRepo;
44 | private final Iterator packs;
45 | private Iterator entries;
46 | private ObjectId objectId;
47 | private ObjectLoader loader;
48 |
49 | private final Map> strFieldGetters = new HashMap<>();
50 |
51 | public ObjectsRecordCursor(List columnHandles, Git repo)
52 | {
53 | this.columnHandles = columnHandles;
54 |
55 | Map nameToIndex = new HashMap<>();
56 | for (int i = 0; i < columnHandles.size(); i++) {
57 | nameToIndex.put(columnHandles.get(i).getColumnName(), i);
58 | }
59 |
60 | Map> getters = Map.of(
61 | "object_id", ObjectsRecordCursor::getObjectId,
62 | "contents", ObjectsRecordCursor::getContents);
63 |
64 | for (Map.Entry> entry : getters.entrySet()) {
65 | String k = entry.getKey();
66 | if (nameToIndex.containsKey(k)) {
67 | strFieldGetters.put(nameToIndex.get(k), entry.getValue());
68 | }
69 | }
70 |
71 | fileRepo = (FileRepository) repo.getRepository();
72 | Collection packs = fileRepo.getObjectDatabase().getPacks();
73 | this.packs = packs.iterator();
74 | }
75 |
76 | @Override
77 | public long getCompletedBytes()
78 | {
79 | return 0;
80 | }
81 |
82 | @Override
83 | public long getReadTimeNanos()
84 | {
85 | return 0;
86 | }
87 |
88 | @Override
89 | public Type getType(int field)
90 | {
91 | checkArgument(field < columnHandles.size(), "Invalid field index");
92 | return columnHandles.get(field).getColumnType();
93 | }
94 |
95 | @Override
96 | public boolean advanceNextPosition()
97 | {
98 | if (packs == null) {
99 | return false;
100 | }
101 | if (entries == null || !entries.hasNext()) {
102 | if (!packs.hasNext()) {
103 | return false;
104 | }
105 | entries = packs.next().iterator();
106 | }
107 |
108 | objectId = entries.next().toObjectId();
109 | try {
110 | loader = fileRepo.open(objectId);
111 | if (loader.getType() != OBJ_BLOB) {
112 | return advanceNextPosition();
113 | }
114 | }
115 | catch (IOException e) {
116 | return advanceNextPosition();
117 | }
118 |
119 | return true;
120 | }
121 |
122 | @Override
123 | public boolean getBoolean(int field)
124 | {
125 | throw new UnsupportedOperationException();
126 | }
127 |
128 | @Override
129 | public long getLong(int field)
130 | {
131 | throw new UnsupportedOperationException();
132 | }
133 |
134 | @Override
135 | public double getDouble(int field)
136 | {
137 | throw new UnsupportedOperationException();
138 | }
139 |
140 | @Override
141 | public Slice getSlice(int field)
142 | {
143 | checkArgument(strFieldGetters.containsKey(field), "Invalid field index");
144 | return strFieldGetters.get(field).apply(this);
145 | }
146 |
147 | @Override
148 | public Object getObject(int field)
149 | {
150 | throw new UnsupportedOperationException();
151 | }
152 |
153 | @Override
154 | public boolean isNull(int field)
155 | {
156 | return false;
157 | }
158 |
159 | @Override
160 | public void close()
161 | {
162 | }
163 |
164 | private Slice getObjectId()
165 | {
166 | return Slices.utf8Slice(objectId.getName());
167 | }
168 |
169 | private Slice getContents()
170 | {
171 | byte[] bytes = loader.getBytes();
172 | return Slices.wrappedBuffer(bytes, 0, bytes.length);
173 | }
174 | }
175 |
--------------------------------------------------------------------------------
/src/main/java/pl/net/was/trino/git/TagsRecordCursor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Licensed under the Apache License, Version 2.0 (the "License");
3 | * you may not use this file except in compliance with the License.
4 | * You may obtain a copy of the License at
5 | *
6 | * http://www.apache.org/licenses/LICENSE-2.0
7 | *
8 | * Unless required by applicable law or agreed to in writing, software
9 | * distributed under the License is distributed on an "AS IS" BASIS,
10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 | * See the License for the specific language governing permissions and
12 | * limitations under the License.
13 | */
14 | package pl.net.was.trino.git;
15 |
16 | import io.airlift.slice.Slice;
17 | import io.airlift.slice.Slices;
18 | import io.trino.spi.connector.RecordCursor;
19 | import io.trino.spi.type.Type;
20 | import org.eclipse.jgit.api.Git;
21 | import org.eclipse.jgit.api.errors.GitAPIException;
22 | import org.eclipse.jgit.errors.IncorrectObjectTypeException;
23 | import org.eclipse.jgit.lib.Ref;
24 | import org.eclipse.jgit.revwalk.RevTag;
25 | import org.eclipse.jgit.revwalk.RevWalk;
26 |
27 | import java.io.IOException;
28 | import java.io.UncheckedIOException;
29 | import java.util.Iterator;
30 | import java.util.List;
31 |
32 | import static com.google.common.base.Preconditions.checkArgument;
33 | import static com.google.common.base.Preconditions.checkState;
34 | import static io.trino.spi.type.DateTimeEncoding.packDateTimeWithZone;
35 | import static io.trino.spi.type.TimeZoneKey.getTimeZoneKey;
36 | import static java.util.Arrays.asList;
37 |
38 | public class TagsRecordCursor
39 | implements RecordCursor
40 | {
41 | private final List columnHandles;
42 | private final int[] fieldToColumnIndex;
43 |
44 | private final boolean parseTag;
45 | private final RevWalk walk;
46 | private Iterator][ tags;
47 |
48 | private List]