Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,17 @@ jobs:
# zstd is required to compile the native library.
submodules: recursive

- name: Set up JDK 11 (MRJAR versioned classes)
# Exports JAVA_HOME_11_X64. The JDK 8 build compiles src/main/java11 with
# it into META-INF/versions/11 (see the antrun execution in core/pom.xml);
# JDK 8 javac cannot compile that source root.
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "11"

- name: Set up JDK 8
# Last setup-java call wins JAVA_HOME: the build itself runs on JDK 8.
uses: actions/setup-java@v4
with:
distribution: temurin
Expand Down Expand Up @@ -60,8 +70,58 @@ jobs:
src/main/resources/io/questdb/client/bin/linux-x86-64/libquestdb.so

- name: Compile, test, and build javadoc
env:
JAVA11_HOME: ${{ env.JAVA_HOME_11_X64 }}
run: mvn -B -ntp -P javadoc clean install

- name: Upload client jar for the cross-JDK smoke job
uses: actions/upload-artifact@v4
with:
name: questdb-client-jar
path: |
core/target/questdb-client-*.jar
!core/target/*-tests.jar
!core/target/*-javadoc.jar
!core/target/*-sources.jar
if-no-files-found: error

# The 1.3.5-1.3.7 releases shipped a JDK 8-built jar whose FdBig class links
# sun.misc.FDBigInteger (gone since Java 9) and whose module name degraded to
# the filename-derived "questdb.client". Both are only observable in the
# PACKAGED jar on a modern JDK, so take the jar built by build-jdk8 and prove
# on JDK 25 that (a) the automatic module name is io.questdb.client and
# (b) slow-path double formatting resolves the META-INF/versions/11 bridge.
mrjar-smoke-jdk25:
name: MRJAR smoke (JDK 8 jar on JDK 25)
needs: build-jdk8
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out
uses: actions/checkout@v4

- name: Set up JDK 25
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "25"

- name: Download JDK 8-built client jar
uses: actions/download-artifact@v4
with:
name: questdb-client-jar
path: client-jar

- name: Check module name and run double-formatting smoke
run: |
jar_file=(client-jar/questdb-client-*.jar)
jar_file="${jar_file[0]}"
jar --describe-module --file "$jar_file" | tee module.txt
grep -q '^io\.questdb\.client@' module.txt
javac -cp "$jar_file" -d smoke-classes \
core/src/test/java/io/questdb/client/test/std/DoubleFormatSmoke.java
java -cp "$jar_file:smoke-classes" io.questdb.client.test.std.DoubleFormatSmoke

# The client is also consumed as a submodule of the main questdb repo, which
# builds on JDK 25. Guard against JDK 25 compile breakage (main + test
# sources, both modules) and confirm the javadoc jar builds on JDK 25 too
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/maven_central_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,17 @@ jobs:
with:
ref: ${{ needs.resolve.outputs.source_sha }}

- name: Set up Java 11 (MRJAR versioned classes)
# Exports JAVA_HOME_11_X64. The JDK 8 build compiles src/main/java11 with
# it into META-INF/versions/11 (see the antrun execution in core/pom.xml);
# JDK 8 javac cannot compile that source root.
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: "11"

- name: Set up Java 8
# Last setup-java call wins JAVA_HOME: the build itself runs on JDK 8.
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
Expand All @@ -487,6 +497,8 @@ jobs:
mvn -B -ntp org.codehaus.mojo:versions-maven-plugin:2.16.2:set -DnewVersion="${RELEASE_VERSION}" -DprocessAllModules=true -DgenerateBackupPoms=false

- name: Verify release artifact (full test suite, native libs bundled)
env:
JAVA11_HOME: ${{ env.JAVA_HOME_11_X64 }}
run: |
# Tests on -- this is the gate. The bundled linux-x86-64 native library
# is exercised by the real test suite before anyone approves the publish.
Expand Down Expand Up @@ -559,7 +571,16 @@ jobs:
exit 1
fi

- name: Set up Java 11 (MRJAR versioned classes)
# Same as the verify job: the deploy below re-packages the jar on JDK 8
# and needs JDK 11 for the META-INF/versions/11 classes.
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
java-version: "11"

- name: Set up Java 8
# Last setup-java call wins JAVA_HOME: the build itself runs on JDK 8.
uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0
with:
distribution: temurin
Expand Down Expand Up @@ -637,6 +658,8 @@ jobs:

- name: Upload signed bundle to Central (validate only, droppable)
id: upload
env:
JAVA11_HOME: ${{ env.JAVA_HOME_11_X64 }}
run: |
# autoPublish=false + waitUntil=validated (set in core/pom.xml) makes the
# build block ONLY on validation (VALIDATING -> VALIDATED, a few minutes;
Expand Down
8 changes: 8 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,14 @@ Build on a JDK 8 to validate the shipping artifact (the source-of-truth
target); also confirm it compiles on a modern JDK (11+) before merging, the
same two fronts CI guards.

Packaging on JDK 8 additionally needs `JAVA11_HOME` pointing at a JDK 11+:
the jar is a Multi-Release jar whose `META-INF/versions/11` classes (the
`src/main/java11` FdBig/Compat bridge) cannot be compiled by JDK 8 javac.
Without them a JDK 8-built jar throws `NoClassDefFoundError:
sun/misc/FDBigInteger` on Java 9+ (the 1.3.5–1.3.7 regression), so the build
fails fast instead of skipping. Test-only runs (`mvn -pl core test`) don't
need it.

The parent `questdb` repo's `local-client` profile pulls this module as a
sub-module so server changes can build against unpublished client code; if
you change client code, install it (or pass `-P local-client` in the parent)
Expand Down
108 changes: 108 additions & 0 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,12 @@
<manifestEntries>
<QuestDB-Client-Build-Commit-Hash>${buildNumber}</QuestDB-Client-Build-Commit-Hash>
<QuestDB-Client-Version>${project.version}</QuestDB-Client-Version>
<!-- JDK 8 release builds cannot compile module-info.java, so the
module name must be pinned here or it degrades to the
filename-derived "questdb.client" (the 1.3.5-1.3.7 regression).
On JDK 11+ builds the real module-info.class takes precedence. -->
<Automatic-Module-Name>io.questdb.client</Automatic-Module-Name>
<Multi-Release>true</Multi-Release>
</manifestEntries>
</archive>
</configuration>
Expand Down Expand Up @@ -185,9 +191,36 @@
</includes>
<excludes>
<exclude>${test.exclude}</exclude>
<!-- *IT classes need the packaged jar; failsafe runs them after `package` -->
<exclude>**/*IT.class</exclude>
</excludes>
</configuration>
</plugin>
<!-- Packaging integration tests (JarPackagingIT): the sun.misc.FDBigInteger /
module-name regressions in 1.3.5-1.3.7 were invisible to unit tests because
they run against target/classes, where Multi-Release selection never
happens. These ITs inspect and execute the packaged jar itself. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<version>3.5.3</version>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<useModulePath>false</useModulePath>
<systemPropertyVariables>
<questdb.client.jar>${project.build.directory}/${project.build.finalName}.jar</questdb.client.jar>
<questdb.client.test.classes>${project.build.testOutputDirectory}</questdb.client.test.classes>
<questdb.client.java11.src>${project.basedir}/src/main/java11</questdb.client.java11.src>
</systemPropertyVariables>
</configuration>
</plugin>
<!-- Adds the JDK-version-specific source root (src/main/java11 or src/main/java8)
so the FDBigInteger bridge / module-access shim compiles on each JDK. -->
<plugin>
Expand Down Expand Up @@ -416,6 +449,30 @@
<id>maven-central-publish</id>
<build>
<plugins>
<!-- The shipped artifact must be the JDK 8 build: a JDK 11+ build puts the
jdk.internal.math FdBig bridge and module-info.class into the jar ROOT,
which breaks Java 8 consumers. Refuse to publish from any other JDK. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.0.0-M3</version>
<executions>
<execution>
<id>enforce-publish-from-jdk8</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<requireJavaVersion>
<version>[1.8,1.9)</version>
<message>questdb-client must be published from JDK 8; a JDK 11+ build produces a jar that breaks Java 8 consumers.</message>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
Expand Down Expand Up @@ -519,10 +576,61 @@
<javac.compile.target>1.8</javac.compile.target>
<!-- logback 1.5.x requires Java 11; the 1.3.x line is the Java 8 compatible branch -->
<logback.version>1.3.15</logback.version>
<!-- JDK used to compile src/main/java11 into META-INF/versions/11 (see the
antrun execution below). Override with -Djava11.home=/path/to/jdk11. -->
<java11.home>${env.JAVA11_HOME}</java11.home>
</properties>
<activation>
<jdk>1.8</jdk>
</activation>
<build>
<plugins>
<!-- JDK 8 javac can only compile the src/main/java8 FdBig bridge
(sun.misc.FDBigInteger), which does not exist on Java 9+. Package the
src/main/java11 bridge (jdk.internal.math.FDBigInteger) into
META-INF/versions/11 so the Multi-Release jar works on every JDK.
Shipping a JDK 8-built jar without this breaks double formatting on
Java 9+ with NoClassDefFoundError (the 1.3.5-1.3.7 regression), hence
a missing JDK 11 fails the build instead of skipping. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-antrun-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>compile-mrjar-versions-11</id>
<phase>prepare-package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<target>
<fail message="Packaging on JDK 8 needs a JDK 11+ to compile src/main/java11 into META-INF/versions/11. Set JAVA11_HOME (or -Djava11.home=...) to a JDK 11+ home.">
<condition>
<not>
<or>
<available file="${java11.home}/bin/javac" />
<available file="${java11.home}/bin/javac.exe" />
</or>
</not>
</condition>
</fail>
<mkdir dir="${project.build.outputDirectory}/META-INF/versions/11" />
<!-- whole source root, not an explicit file list: a file
added to src/main/java11 must ship in versions/11 or
the jar is broken on Java 9+ again (JarPackagingIT
cross-checks the packaged jar against the source root) -->
<javac srcdir="${project.basedir}/src/main/java11" destdir="${project.build.outputDirectory}/META-INF/versions/11" classpath="${project.build.outputDirectory}" source="11" target="11" encoding="UTF-8" debug="true" includeantruntime="false" fork="true" executable="${java11.home}/bin/javac" compiler="extJavac">
<compilerarg value="--add-exports" />
<compilerarg value="java.base/jdk.internal.math=ALL-UNNAMED" />
</javac>
</target>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.jetbrains</groupId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*+*****************************************************************************
* ___ _ ____ ____
* / _ \ _ _ ___ ___| |_| _ \| __ )
* | | | | | | |/ _ \/ __| __| | | | _ \
* | |_| | |_| | __/\__ \ |_| |_| | |_) |
* \__\_\\__,_|\___||___/\__|____/|____/
*
* Copyright (c) 2014-2019 Appsicle
* Copyright (c) 2019-2026 QuestDB
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
******************************************************************************/

package io.questdb.client.test.std;

import io.questdb.client.std.str.StringSink;

/**
* Standalone (no JUnit) smoke check that double formatting works against the
* packaged jar on the running JDK. The extreme-exponent values exercise the
* FdBig fallback of {@code Numbers.appendDouble0}, which resolves
* {@code FDBigInteger} from a different JDK-internal package on Java 8 vs 9+;
* a jar packaged with the wrong bridge dies here with
* {@code NoClassDefFoundError: sun/misc/FDBigInteger} (the 1.3.5-1.3.7
* regression). Run by {@link JarPackagingIT} on the build JDK and by CI on
* JDK 25 against the JDK 8-built jar.
*/
public final class DoubleFormatSmoke {

public static void main(String[] args) {
double[] values = {
0.0d,
123.456d,
// FdBig slow-path values
1.0E-300,
Double.MIN_VALUE,
Double.MAX_VALUE,
-2.225073858507201E-308,
1.1317400099603851E308
};
for (int i = 0; i < values.length; i++) {
double d = values[i];
StringSink sink = new StringSink();
sink.put(d);
String formatted = sink.toString();
if (Double.doubleToLongBits(Double.parseDouble(formatted)) != Double.doubleToLongBits(d)) {
System.err.println("FAIL: " + d + " formatted as \"" + formatted + "\" does not round-trip");
System.exit(1);
}
}
System.out.println("OK: double formatting works on Java " + System.getProperty("java.version"));
}

private DoubleFormatSmoke() {
}
}
Loading
Loading