# ailake-jni

C-ABI cdylib that exposes the [AI-Lake](https://github.com/ThiagoLange/ai-lakehouse) vector search engine to JVM runtimes (Spark, Trino, Flink) via [JNA](https://github.com/java-native-access/jna).

## Overview

`ailake-jni` compiles to a single shared library (`libailake_jni.so` / `ailake_jni.dll`) that all JVM plugins load at runtime. The API uses a **JSON-envelope pattern** — callers pass a JSON request string and receive a JSON response string — making it callable from any JVM language (Scala, Kotlin, Java) without code generation.

## Exported C-ABI functions

### `ailake_search_json`

```
fn ailake_search_json(request_json: *const c_char) -> *mut c_char
```

Performs a nearest-neighbor vector search on a local AI-Lake table.

**Request JSON:**

```json
{
  "warehouse": "/path/to/warehouse",
  "namespace": "default",
  "table":     "my_table",
  "vec_col":   "embedding",
  "dim":       1536,
  "query":     [0.1, -0.2, 0.3, "..."],
  "top_k":     10,
  "ef_search": 50
}
```

**Response JSON:**

```json
{
  "ok": true,
  "results": [
    { "row_id": 42, "distance": 0.123, "file_path": "data/part-00001.parquet" }
  ]
}
```

On error: `{"ok": false, "error": "..."}`.

---

### `ailake_write_batch_json`

```
fn ailake_write_batch_json(request_json: *const c_char) -> *mut c_char
```

Writes a batch of records and their embeddings to an AI-Lake table.

**Request JSON:**

```json
{
  "warehouse": "/path/to/warehouse",
  "namespace": "default",
  "table":     "my_table",
  "vec_col":   "embedding",
  "dim":       1536,
  "metric":    "cosine",
  "precision": "f16",
  "ids":        [1, 2, 3],
  "embeddings": [[0.1, 0.2, "..."], [0.3, 0.4, "..."], [0.5, 0.6, "..."]]
}
```

**Response JSON:** `{"ok": true, "snapshot_id": 7}` or `{"ok": false, "error": "..."}`.

---

### `ailake_free_string`

```
fn ailake_free_string(ptr: *mut c_char)
```

Frees a string returned by any of the above functions. **Always call this** after consuming the response — the JVM garbage collector cannot free Rust-allocated memory.

---

### `ailake_version`

```
fn ailake_version() -> *const c_char
```

Returns the library version as a static null-terminated string. Do **not** free this pointer.

## Usage from JVM plugins

### Kotlin / Trino (JNA)

```kotlin
import com.sun.jna.Library
import com.sun.jna.Native
import com.sun.jna.Pointer

interface AilakeLib : Library {
    fun ailake_search_json(requestJson: String): Pointer
    fun ailake_free_string(ptr: Pointer)
}

val lib: AilakeLib = Native.load("ailake_jni", AilakeLib::class.java)

val request = """{"warehouse":"/data/lake","namespace":"default","table":"docs",
    "vec_col":"embedding","dim":1536,"query":[...],"top_k":10}"""

val ptr = lib.ailake_search_json(request)
val json = ptr.getString(0)
lib.ailake_free_string(ptr)
// parse json...
```

### Scala / Spark (JNA)

```scala
import com.sun.jna.{Library, Native, Pointer}

trait AilakeLib extends Library {
  def ailake_search_json(requestJson: String): Pointer
  def ailake_free_string(ptr: Pointer): Unit
}

val lib = Native.load("ailake_jni", classOf[AilakeLib]).asInstanceOf[AilakeLib]

val ptr = lib.ailake_search_json(requestJson)
val json = ptr.getString(0)
lib.ailake_free_string(ptr)
```

## Library path

The `.so` / `.dll` must be on the native library search path before the JVM starts:

```bash
# Linux
export LD_LIBRARY_PATH=/path/to/lib:$LD_LIBRARY_PATH

# macOS
export DYLD_LIBRARY_PATH=/path/to/lib:$DYLD_LIBRARY_PATH

# JVM flag (all platforms)
-Djava.library.path=/path/to/lib
```

## Building

```bash
cargo build --release -p ailake-jni
# output: target/release/libailake_jni.so  (Linux)
#         target/release/ailake_jni.dll    (Windows)
#         target/release/libailake_jni.dylib (macOS)
```

## License

Licensed under either of [Apache License, Version 2.0](../LICENSE-APACHE) or [MIT License](../LICENSE-MIT) at your option.
