Skip to content

Commit 6929ab3

Browse files
committed
feat(core): set Substrait version and producer
Signed-off-by: Niels Pardon <par@zurich.ibm.com>
1 parent bbe77ef commit 6929ab3

File tree

10 files changed

+213
-20
lines changed

10 files changed

+213
-20
lines changed

.github/workflows/pr.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,11 @@ jobs:
6969
- name: Setup Gradle
7070
uses: gradle/actions/setup-gradle@v4
7171
- name: Build with Gradle
72-
run: gradle build --rerun-tasks
72+
run: |
73+
# fetch submodule tags since actions/checkout@v4 does not
74+
git submodule foreach 'git fetch --unshallow || true'
75+
76+
gradle build --rerun-tasks
7377
examples:
7478
name: Build Examples
7579
runs-on: ubuntu-latest
@@ -115,6 +119,9 @@ jobs:
115119
run: gu install native-image
116120
- name: Build with Gradle
117121
run: |
122+
# fetch submodule tags since actions/checkout@v4 does not
123+
git submodule foreach 'git fetch --unshallow || true'
124+
118125
gradle nativeImage
119126
- name: Smoke Test
120127
run: |

.github/workflows/release.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,11 @@ jobs:
3535
- name: Install GraalVM native image
3636
run: gu install native-image
3737
- name: Build with Gradle
38-
run: gradle nativeImage
38+
run: |
39+
# fetch submodule tags since actions/checkout@v4 does not
40+
git submodule foreach 'git fetch --unshallow || true'
41+
42+
gradle nativeImage
3943
- name: Smoke Test
4044
run: |
4145
./isthmus-cli/src/test/script/smoke.sh

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ out/**
1212

1313
*/bin
1414
.metals
15+
.bloop

ci/release/publish.sh

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@
33

44
set -euo pipefail
55

6+
# ensure the submodule tags exist
7+
git submodule foreach 'git fetch --unshallow || true'
8+
69
gradle wrapper
710
./gradlew clean :core:publishToSonatype :isthmus:publishToSonatype :spark:publishToSonatype closeAndReleaseSonatypeStagingRepository

core/build.gradle.kts

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
import java.io.ByteArrayOutputStream
2+
import java.nio.charset.StandardCharsets
3+
import org.gradle.api.provider.ValueSource
4+
import org.gradle.api.provider.ValueSourceParameters
15
import org.gradle.plugins.ide.idea.model.IdeaModel
6+
import org.slf4j.LoggerFactory
27

38
plugins {
49
`maven-publish`
@@ -105,6 +110,71 @@ configurations[JavaPlugin.API_CONFIGURATION_NAME].let { apiConfiguration ->
105110
apiConfiguration.setExtendsFrom(apiConfiguration.extendsFrom.filter { it.name != "antlr" })
106111
}
107112

113+
abstract class SubstraitSpecVersionValueSource :
114+
ValueSource<String, SubstraitSpecVersionValueSource.Parameters> {
115+
companion object {
116+
val logger = LoggerFactory.getLogger("SubstraitSpecVersionValueSource")
117+
}
118+
119+
interface Parameters : ValueSourceParameters {
120+
val substraitDirectory: Property<File>
121+
}
122+
123+
@get:Inject abstract val execOperations: ExecOperations
124+
125+
override fun obtain(): String {
126+
val stdOutput = ByteArrayOutputStream()
127+
val errOutput = ByteArrayOutputStream()
128+
execOperations.exec {
129+
commandLine("git", "describe", "--tags")
130+
standardOutput = stdOutput
131+
errorOutput = errOutput
132+
setIgnoreExitValue(true)
133+
workingDir = parameters.substraitDirectory.get()
134+
}
135+
136+
// capturing the error output and logging it to avoid issues with VS Code Spotless plugin
137+
val error = String(errOutput.toByteArray())
138+
if (error != "") {
139+
logger.warn(error)
140+
}
141+
142+
val cmdOut = String(stdOutput.toByteArray()).trim()
143+
144+
if (cmdOut.startsWith("v")) {
145+
return cmdOut.substring(1)
146+
}
147+
148+
return cmdOut
149+
}
150+
}
151+
152+
tasks.register("writeManifest") {
153+
doLast {
154+
val substraitSpecVersionProvider =
155+
providers.of(SubstraitSpecVersionValueSource::class) {
156+
parameters.substraitDirectory.set(project(":").file("substrait"))
157+
}
158+
159+
val manifestFile =
160+
layout.buildDirectory
161+
.file("generated/sources/manifest/META-INF/MANIFEST.MF")
162+
.get()
163+
.getAsFile()
164+
manifestFile.getParentFile().mkdirs()
165+
166+
manifestFile.printWriter(StandardCharsets.UTF_8).use {
167+
it.println("Manifest-Version: 1.0")
168+
it.println("Implementation-Title: substrait-java")
169+
it.println("Implementation-Version: " + project.version)
170+
it.println("Specification-Title: substrait")
171+
it.println("Specification-Version: " + substraitSpecVersionProvider.get())
172+
}
173+
}
174+
}
175+
176+
tasks.named("compileJava") { dependsOn("writeManifest") }
177+
108178
tasks {
109179
shadowJar {
110180
archiveClassifier.set("") // to override ".jar" instead of producing "-all.jar"
@@ -114,6 +184,8 @@ tasks {
114184
// rename the shadowed deps so that they don't conflict with consumer's own deps
115185
relocate("org.antlr.v4.runtime", "io.substrait.org.antlr.v4.runtime")
116186
}
187+
188+
jar { manifest { from("build/generated/sources/manifest/META-INF/MANIFEST.MF") } }
117189
}
118190

119191
java {
@@ -132,6 +204,7 @@ sourceSets {
132204
main {
133205
proto.srcDir("../substrait/proto")
134206
resources.srcDir("../substrait/extensions")
207+
resources.srcDir("build/generated/sources/manifest/")
135208
java.srcDir(file("build/generated/sources/antlr/main/java/"))
136209
}
137210
}

core/src/main/java/io/substrait/plan/Plan.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22

33
import io.substrait.proto.AdvancedExtension;
44
import io.substrait.relation.Rel;
5+
import java.io.IOException;
56
import java.util.List;
67
import java.util.Optional;
8+
import java.util.jar.Attributes.Name;
9+
import java.util.jar.Manifest;
710
import org.immutables.value.Value;
811

912
@Value.Immutable
1013
public abstract class Plan {
1114

15+
@Value.Default
16+
public Version getVersion() {
17+
return ImmutableVersion.builder().build();
18+
}
19+
1220
public abstract List<Root> getRoots();
1321

1422
public abstract List<String> getExpectedTypeUrls();
@@ -19,6 +27,58 @@ public static ImmutablePlan.Builder builder() {
1927
return ImmutablePlan.builder();
2028
}
2129

30+
@Value.Immutable
31+
public abstract static class Version {
32+
private static final String[] VERSION_COMPONENTS = loadVersion();
33+
34+
@Value.Default
35+
public int getMajor() {
36+
return Integer.parseInt(VERSION_COMPONENTS[0]);
37+
}
38+
39+
@Value.Default
40+
public int getMinor() {
41+
return Integer.parseInt(VERSION_COMPONENTS[1]);
42+
}
43+
44+
@Value.Default
45+
public int getPatch() {
46+
return Integer.parseInt(VERSION_COMPONENTS[2]);
47+
}
48+
49+
@SuppressWarnings("immutables:untype")
50+
@Value.Default
51+
public Optional<String> getGitHash() {
52+
return Optional.ofNullable(null);
53+
}
54+
55+
@SuppressWarnings("immutables:untype")
56+
@Value.Default
57+
public Optional<String> getProducer() {
58+
return Optional.of("substrait-java");
59+
}
60+
61+
private static String[] loadVersion() {
62+
// load the specification version from the JAR manifest
63+
String specificationVersion = Version.class.getPackage().getSpecificationVersion();
64+
65+
// load the manifest directly from the classpath if the specification version is null which is
66+
// the case if the Version class is not in a JAR, e.g. during the Gradle build
67+
if (specificationVersion == null) {
68+
try {
69+
Manifest manifest =
70+
new Manifest(
71+
Version.class.getClassLoader().getResourceAsStream("META-INF/MANIFEST.MF"));
72+
specificationVersion = manifest.getMainAttributes().getValue(Name.SPECIFICATION_VERSION);
73+
} catch (IOException e) {
74+
throw new IllegalStateException("Could not load version from manifest", e);
75+
}
76+
}
77+
78+
return specificationVersion.split("\\.");
79+
}
80+
}
81+
2282
@Value.Immutable
2383
public abstract static class Root {
2484
public abstract Rel getInput();

core/src/main/java/io/substrait/plan/PlanProtoConverter.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.substrait.proto.Plan;
55
import io.substrait.proto.PlanRel;
66
import io.substrait.proto.Rel;
7+
import io.substrait.proto.Version;
78
import io.substrait.relation.RelProtoConverter;
89
import java.util.ArrayList;
910
import java.util.List;
@@ -34,6 +35,18 @@ public Plan toProto(io.substrait.plan.Plan plan) {
3435
if (plan.getAdvancedExtension().isPresent()) {
3536
builder.setAdvancedExtensions(plan.getAdvancedExtension().get());
3637
}
38+
39+
Version.Builder versionBuilder =
40+
Version.newBuilder()
41+
.setMajorNumber(plan.getVersion().getMajor())
42+
.setMinorNumber(plan.getVersion().getMinor())
43+
.setPatchNumber(plan.getVersion().getPatch());
44+
45+
plan.getVersion().getGitHash().ifPresent(gh -> versionBuilder.setGitHash(gh));
46+
plan.getVersion().getProducer().ifPresent(p -> versionBuilder.setProducer(p));
47+
48+
builder.setVersion(versionBuilder);
49+
3750
return builder.build();
3851
}
3952
}

core/src/main/java/io/substrait/plan/ProtoPlanConverter.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,29 @@ public Plan from(io.substrait.proto.Plan plan) {
3939
Rel rel = relConverter.from(root.getInput());
4040
roots.add(Plan.Root.builder().input(rel).names(root.getNamesList()).build());
4141
}
42+
43+
ImmutableVersion.Builder versionBuilder =
44+
ImmutableVersion.builder()
45+
.major(plan.getVersion().getMajorNumber())
46+
.minor(plan.getVersion().getMinorNumber())
47+
.patch(plan.getVersion().getPatchNumber());
48+
49+
// protobuf field 'git_hash' is an empty string by default
50+
if (!"".equals(plan.getVersion().getGitHash())) {
51+
versionBuilder.gitHash(Optional.of(plan.getVersion().getGitHash()));
52+
}
53+
54+
// protobuf field 'producer' is an empty string by default
55+
if (!"".equals(plan.getVersion().getProducer())) {
56+
versionBuilder.producer(Optional.of(plan.getVersion().getProducer()));
57+
}
58+
4259
return Plan.builder()
4360
.roots(roots)
4461
.expectedTypeUrls(plan.getExpectedTypeUrlsList())
4562
.advancedExtension(
4663
Optional.ofNullable(plan.hasAdvancedExtensions() ? plan.getAdvancedExtensions() : null))
64+
.version(versionBuilder.build())
4765
.build();
4866
}
4967
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package io.substrait.relation;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
6+
import io.substrait.plan.ImmutableVersion;
7+
import java.util.Optional;
8+
import org.junit.jupiter.api.Test;
9+
10+
public class SpecVersionTest {
11+
@Test
12+
public void testSubstraitVersionDefaultValues() {
13+
ImmutableVersion version = ImmutableVersion.builder().build();
14+
15+
assertNotNull(version.getMajor());
16+
assertNotNull(version.getMinor());
17+
assertNotNull(version.getPatch());
18+
19+
assertEquals(Optional.of("substrait-java"), version.getProducer());
20+
}
21+
}

isthmus/src/main/java/io/substrait/isthmus/SqlToSubstrait.java

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
package io.substrait.isthmus;
22

33
import com.google.common.annotations.VisibleForTesting;
4-
import io.substrait.extension.ExtensionCollector;
4+
import io.substrait.plan.ImmutablePlan.Builder;
5+
import io.substrait.plan.PlanProtoConverter;
56
import io.substrait.proto.Plan;
6-
import io.substrait.proto.PlanRel;
7-
import io.substrait.relation.RelProtoConverter;
87
import java.util.List;
98
import org.apache.calcite.plan.hep.HepPlanner;
109
import org.apache.calcite.plan.hep.HepProgram;
@@ -56,22 +55,16 @@ List<RelRoot> sqlToRelNode(String sql, List<String> tables) throws SqlParseExcep
5655

5756
private Plan executeInner(String sql, SqlValidator validator, Prepare.CatalogReader catalogReader)
5857
throws SqlParseException {
59-
var plan = Plan.newBuilder();
60-
ExtensionCollector functionCollector = new ExtensionCollector();
61-
var relProtoConverter = new RelProtoConverter(functionCollector);
58+
Builder builder = io.substrait.plan.Plan.builder();
59+
6260
// TODO: consider case in which one sql passes conversion while others don't
63-
sqlToRelNode(sql, validator, catalogReader)
64-
.forEach(
65-
root -> {
66-
plan.addRelations(
67-
PlanRel.newBuilder()
68-
.setRoot(
69-
relProtoConverter.toProto(
70-
SubstraitRelVisitor.convert(
71-
root, EXTENSION_COLLECTION, featureBoard))));
72-
});
73-
functionCollector.addExtensionsToPlan(plan);
74-
return plan.build();
61+
sqlToRelNode(sql, validator, catalogReader).stream()
62+
.map(root -> SubstraitRelVisitor.convert(root, EXTENSION_COLLECTION, featureBoard))
63+
.forEach(root -> builder.addRoots(root));
64+
65+
PlanProtoConverter planToProto = new PlanProtoConverter();
66+
67+
return planToProto.toProto(builder.build());
7568
}
7669

7770
private List<RelRoot> sqlToRelNode(

0 commit comments

Comments
 (0)