You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 7407c75
Browse filesBrowse the repository at this point in the historyBrowse files
A Java client is Java and the engine is a shared library, so until now
using this meant installing a libzu by hand and telling the JVM where it
went. One more dependency now does it: zudb-native carries a build for
every platform this client supports, Library.find looks for one on the
class path, and a resource is copied to a temp file because no loader on
any platform can map a resource.
Eight artifacts come out of one module and one staged directory, so they
cannot disagree about what is in them. The one with no classifier holds
all seven platforms and is what a program developed on a laptop and
deployed to a cluster wants, and the only one that survives being shaded.
The seven classified ones hold one platform each, which is what a
container image that knows what it runs on wants.
Alpine is a platform rather than a smaller Linux: a shared object built
against glibc does not load on musl, and says so in a message about an
interpreter rather than about a database. Library.flavour picks between
the two by looking for musl's own loader, which is the one path the ABI
fixes rather than a distribution.
Two things a user meets go with it. On the module path nothing requires
this artifact, because there is no code in it to require, so a jar that
is on the path is never resolved and its library is invisible; the search
notices it is on a module path and names --add-modules dev.zudb.natives
rather than leaving a user to work out why a class path run worked. And
the derived automatic module name would be zudb.native, which is not a
legal module name because native is a keyword, so every jar carries
Automatic-Module-Name in its manifest.
The search now also carries the places it ruled out, and Zu.source says
which of the four answered, because a path on its own does not explain
why the wrong engine is loaded and a property somebody set three shells
ago is exactly the thing that explains it.
Nothing in the test suite could check the claim this makes, since the
suite is told where the library is so that it tests the binding rather
than the search. So CI checks it the way a user meets it, on Linux and
macOS: a class path, no property, no environment variable, and a
statement that has to answer.
The keys and the parsing belong to the engine rather than to this client, so a key added to the engine since this client was built works anyway, and a key that never existed is refused with the typo named. A suffix such as `MB` is deliberately not parsed anywhere: its two readings differ by 4.9%, and the place to decide which one a user meant is where the user typed it.
187
187
188
+
## The engine, without installing one
189
+
190
+
The client is Java and the engine is a shared library, so something has to put a `libzu` on the machine. Adding one more dependency is that something:
191
+
192
+
```xml
193
+
<dependency>
194
+
<groupId>dev.zudb</groupId>
195
+
<artifactId>zudb-native</artifactId>
196
+
<version>${zu.version}</version>
197
+
<scope>runtime</scope>
198
+
</dependency>
199
+
```
200
+
201
+
That artifact carries a build for every platform this client supports and is about twenty megabytes. It is the right one for a program developed on a laptop and deployed to a cluster, and the only one that survives being shaded into an uber-jar. A container image knows exactly what it runs on, so it can name a platform and take about three megabytes instead:
202
+
203
+
| Classifier | What it holds |
204
+
|---|---|
205
+
|`linux-amd64`| glibc, x86-64 |
206
+
|`linux-arm64`| glibc, aarch64 |
207
+
|`linux-amd64-musl`| musl, x86-64 |
208
+
|`linux-arm64-musl`| musl, aarch64 |
209
+
|`darwin-amd64`| macOS, Intel |
210
+
|`darwin-arm64`| macOS, Apple silicon |
211
+
|`windows-amd64`| Windows, x86-64 |
212
+
213
+
Alpine is a separate row rather than a smaller Linux, because a shared object built against glibc does not load on musl and the message it fails with talks about an interpreter rather than about a database. Which of the two a JVM gets is decided by looking for musl's own loader on disk, which is the one path the ABI fixes rather than a distribution.
214
+
215
+
The library inside the jar is a resource, and no loader on any platform can map one of those, so it is copied to a temp file the first time anything needs it and the copy is what gets loaded. That happens once per JVM.
216
+
217
+
On the module path the artifact needs `--add-modules dev.zudb.natives`. Nothing `requires` it, since there is no code in it to require, and a jar nothing requires is a jar that is never resolved and whose resources are therefore invisible. The search says so itself when it comes up empty on a module path, so the failure names the flag rather than leaving a user to work out why the same classpath run worked.
218
+
188
219
## How it binds
189
220
190
221
The Foreign Function and Memory API is the primary path. The downcall handles are written by hand against `zu.h` rather than generated with `jextract`, because the C ABI here is around seventy functions with a stable shape, and a hand-written layer is where the interesting decisions live: which calls are `Linker.Option.critical` because they are short pure accessors, where the out-parameter scratch space comes from so that a query does not allocate, and how a `zu_error` becomes a typed Java exception exactly once. There is no native code in this repository beyond `libzu` itself.
@@ -196,7 +227,7 @@ An SDK that requires a recent JDK in 2026 excludes a large part of the enterpris
196
227
|`dev.zudb:zudb`| Java 17 | the API, no native code, no FFM types in the public surface |
197
228
|`dev.zudb:zudb-ffm`| Java 25 | the FFM provider, selected automatically |
198
229
|`dev.zudb:zudb-jni`| Java 17 | the fallback provider |
199
-
|`dev.zudb:zudb-native-{platform}`|| the `libzu` binaries |
230
+
|`dev.zudb:zudb-native`|| the `libzu` binaries, all platforms or one by classifier|
200
231
201
232
A `ServiceLoader` picks the provider at run time and application code never names one. The FFM artifact targets Java 25 rather than the Java 22 that finalised the API, because 22 has been out of support since September 2024 and shipping against an unsupported release only moves the problem. CI runs 17, 21, 25, and 26.
202
233
@@ -226,7 +257,16 @@ The engine has no DDL yet, so there is no `CREATE NODE TABLE` and no statement i
226
257
mvn test -Dzu.library=/path/to/libzu.dylib
227
258
```
228
259
229
-
The provider looks at `-Dzu.library`, then `ZU_LIBRARY`, then the platform library path. The tests skip rather than fail when no `libzu` is reachable, so a checkout with no engine build beside it is still green.
260
+
The library is looked for in four places, in order: `-Dzu.library`, then `ZU_LIBRARY`, then a `zudb-native` artifact on the class path, then the platform's own search. A named path is first because a bisect and a bug report both start by pointing this at a build, and the platform's search is last because it is the one that can pick up a library nobody in the process chose. `Zu.library()` and `Zu.source()` say which file was loaded and which of the four it came from, and a failure to bind lists what was ruled out on the way. The tests skip rather than fail when no `libzu` is reachable, so a checkout with no engine build beside it is still green.
261
+
262
+
The `zudb-native` module is not built unless it is asked for, because what it packages is downloaded rather than compiled:
263
+
264
+
```sh
265
+
scripts/stage-natives.sh v0.11.0
266
+
mvn -Pnatives package -DskipTests
267
+
```
268
+
269
+
The argument is a release tag of the engine, which is fetched with `gh`, or a directory that already holds the archives.
0 commit comments