# SynaDB
[](https://github.com/gtava5813/SynaDB/actions/workflows/ci.yml)
[](https://crates.io/crates/synadb)
[](https://opensource.org/licenses/MIT)
> An AI-native embedded database.
An embedded, log-structured, columnar-mapped database engine written in Rust. Syna combines the embedded simplicity of SQLite, the columnar analytical speed of DuckDB, and the schema flexibility of MongoDB.
## Features
- **Append-only log structure** - Fast sequential writes, immutable history
- **Schema-free** - Store heterogeneous data types without migrations
- **AI/ML optimized** - Extract time-series data as contiguous tensors for PyTorch/TensorFlow
- **C-ABI interface** - Use from Python, Node.js, C++, or any FFI-capable language
- **Delta & LZ4 compression** - Minimize storage for time-series data
- **Crash recovery** - Automatic index rebuild on open
- **Thread-safe** - Concurrent read/write access with mutex-protected writes
## Installation
### Building from Source
```bash
# Clone the repository
git clone https://github.com/gtava5813/SynaDB.git
cd SynaDB
# Build release version
cargo build --release
# Run tests
cargo test
```
The compiled library will be at:
- Linux: `target/release/libsynadb.so`
- macOS: `target/release/libsynadb.dylib`
- Windows: `target/release/synadb.dll`
### Adding as a Dependency
```toml
[dependencies]
synadb = "0.1.0"
```
## Quick Start
### Rust Usage
```rust
use synadb::{synadb, Atom, Result};
fn main() -> Result<()> {
// Open or create a database
let mut db = synadb::new("my_data.db")?;
// Write different data types
db.append("temperature", Atom::Float(23.5))?;
db.append("count", Atom::Int(42))?;
db.append("name", Atom::Text("sensor-1".to_string()))?;
db.append("raw_data", Atom::Bytes(vec![0x01, 0x02, 0x03]))?;
// Read values back
if let Some(temp) = db.get("temperature")? {
println!("Temperature: {:?}", temp);
}
// Append more values to build history
db.append("temperature", Atom::Float(24.1))?;
db.append("temperature", Atom::Float(24.8))?;
// Extract history as tensor for ML
let history = db.get_history_floats("temperature")?;
println!("Temperature history: {:?}", history); // [23.5, 24.1, 24.8]
// Delete a key
db.delete("count")?;
assert!(db.get("count")?.is_none());
// List all keys
let keys = db.keys();
println!("Keys: {:?}", keys);
// Compact to reclaim space
db.compact()?;
// Close (optional - happens on drop)
db.close()?;
Ok(())
}
```
### Python Usage (ctypes)
```python
import ctypes
from ctypes import c_char_p, c_double, c_int64, c_int32, c_size_t, POINTER, byref
# Load the library
lib = ctypes.CDLL("./target/release/libsynadb.so") # or .dylib/.dll
# Define function signatures
lib.syna_open.argtypes = [c_char_p]
lib.syna_open.restype = c_int32
lib.syna_close.argtypes = [c_char_p]
lib.syna_close.restype = c_int32
lib.syna_put_float.argtypes = [c_char_p, c_char_p, c_double]
lib.syna_put_float.restype = c_int64
lib.syna_get_float.argtypes = [c_char_p, c_char_p, POINTER(c_double)]
lib.syna_get_float.restype = c_int32
lib.syna_get_history_tensor.argtypes = [c_char_p, c_char_p, POINTER(c_size_t)]
lib.syna_get_history_tensor.restype = POINTER(c_double)
lib.syna_free_tensor.argtypes = [POINTER(c_double), c_size_t]
lib.syna_free_tensor.restype = None
lib.syna_delete.argtypes = [c_char_p, c_char_p]
lib.syna_delete.restype = c_int32
# Usage
db_path = b"my_data.db"
# Open database
result = lib.syna_open(db_path)
assert result == 1, f"Failed to open database: {result}"
# Write float values
lib.syna_put_float(db_path, b"temperature", 23.5)
lib.syna_put_float(db_path, b"temperature", 24.1)
lib.syna_put_float(db_path, b"temperature", 24.8)
# Read latest value
value = c_double()
result = lib.syna_get_float(db_path, b"temperature", byref(value))
if result == 1:
print(f"Temperature: {value.value}")
# Get history as numpy-compatible array
length = c_size_t()
ptr = lib.syna_get_history_tensor(db_path, b"temperature", byref(length))
if ptr:
# Convert to Python list (or use numpy.ctypeslib for zero-copy)
history = [ptr[i] for i in range(length.value)]
print(f"History: {history}")
# Free the tensor memory
lib.syna_free_tensor(ptr, length)
# Close database
lib.syna_close(db_path)
```
### C/C++ Usage
```c
#include "synadb.h"
#include <stdio.h>
int main() {
const char* db_path = "my_data.db";
// Open database
int result = syna_open(db_path);
if (result != 1) {
fprintf(stderr, "Failed to open database: %d\n", result);
return 1;
}
// Write values
syna_put_float(db_path, "temperature", 23.5);
syna_put_float(db_path, "temperature", 24.1);
syna_put_int(db_path, "count", 42);
syna_put_text(db_path, "name", "sensor-1");
// Read float value
double temp;
if (syna_get_float(db_path, "temperature", &temp) == 1) {
printf("Temperature: %f\n", temp);
}
// Get history tensor for ML
size_t len;
double* tensor = syna_get_history_tensor(db_path, "temperature", &len);
if (tensor) {
printf("History (%zu values):", len);
for (size_t i = 0; i < len; i++) {
printf(" %f", tensor[i]);
}
printf("\n");
// Free tensor memory
syna_free_tensor(tensor, len);
}
// Delete a key
syna_delete(db_path, "count");
// Compact database
syna_compact(db_path);
// Close database
syna_close(db_path);
return 0;
}
```
Compile with:
```bash
gcc -o myapp myapp.c -L./target/release -lsynadb -Wl,-rpath,./target/release
```
## Data Types
Syna supports five atomic data types:
| Null | `Atom::Null` | N/A | Absence of value |
| Float | `Atom::Float(f64)` | `syna_put_float` | 64-bit floating point |
| Int | `Atom::Int(i64)` | `syna_put_int` | 64-bit signed integer |
| Text | `Atom::Text(String)` | `syna_put_text` | UTF-8 string |
| Bytes | `Atom::Bytes(Vec<u8>)` | `syna_put_bytes` | Raw byte array |
## Configuration
```rust
use synadb::{synadb, DbConfig};
let config = DbConfig {
enable_compression: true, // LZ4 compression for large values
enable_delta: true, // Delta encoding for float sequences
sync_on_write: true, // fsync after each write (safer but slower)
};
let db = synadb::with_config("my_data.db", config)?;
```
## Error Codes (FFI)
| 1 | `ERR_SUCCESS` | Operation successful |
| 0 | `ERR_GENERIC` | Generic error |
| -1 | `ERR_DB_NOT_FOUND` | Database not in registry |
| -2 | `ERR_INVALID_PATH` | Invalid path or UTF-8 |
| -3 | `ERR_IO` | I/O error |
| -4 | `ERR_SERIALIZATION` | Serialization error |
| -5 | `ERR_KEY_NOT_FOUND` | Key not found |
| -6 | `ERR_TYPE_MISMATCH` | Type mismatch on read |
| -100 | `ERR_INTERNAL_PANIC` | Internal panic |
## Architecture
Syna uses an append-only log structure inspired by the "physics of time" principle:
```
┌─────────────────────────────────────────────────────────────┐
│ Entry 0 │
├──────────────┬──────────────────┬───────────────────────────┤
│ LogHeader │ Key (UTF-8) │ Value (bincode) │
│ (15 bytes) │ (key_len bytes) │ (val_len bytes) │
├──────────────┴──────────────────┴───────────────────────────┤
│ Entry 1 ... │
└─────────────────────────────────────────────────────────────┘
```
- **Writes**: Always append to end of file (sequential I/O)
- **Reads**: Use in-memory index for O(1) key lookup
- **Recovery**: Scan file on open to rebuild index
- **Compaction**: Rewrite file with only latest values
## Contributing
See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
## License
MIT License - see [LICENSE](LICENSE) for details.