feat(clients): professional clients with tests, CI/CD, and examples
- Python: pyproject.toml, pytest suite (unit + integration), README, examples - JavaScript: package.json, TypeScript definitions, node:test suite, README, examples - Nim: integration tests, examples, README, improved nimble tasks - Rust: fixed nodelay/localhost IPv6 bug, integration tests, examples, README - Added GitHub Actions CI for all 4 clients against Docker server - Added docker-compose.test.yml for local integration testing - Added .gitignore for each client
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
/target
|
||||
Cargo.lock
|
||||
*.rs.bk
|
||||
*.pdb
|
||||
Generated
-7
@@ -1,7 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "baradb"
|
||||
version = "0.1.0"
|
||||
+15
-2
@@ -1,8 +1,21 @@
|
||||
[package]
|
||||
name = "baradb"
|
||||
version = "0.1.0"
|
||||
version = "1.0.0"
|
||||
edition = "2021"
|
||||
description = "BaraDB client library for Rust — binary protocol client"
|
||||
authors = ["BaraDB Team <team@baradb.dev>"]
|
||||
description = "Official Rust client for BaraDB — binary protocol client"
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/barabadb/baradadb"
|
||||
documentation = "https://docs.baradb.dev"
|
||||
readme = "README.md"
|
||||
keywords = ["database", "baradb", "multimodal", "client", "wire-protocol"]
|
||||
categories = ["database", "network-programming"]
|
||||
rust-version = "1.70"
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
[[example]]
|
||||
name = "basic"
|
||||
path = "examples/basic.rs"
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
# BaraDB Rust Client
|
||||
|
||||
Official Rust client for **BaraDB** — a multimodal database engine written in Nim.
|
||||
|
||||
## Features
|
||||
|
||||
- **Binary wire protocol** — fast TCP communication using only `std`
|
||||
- **Zero dependencies** — no external crates required
|
||||
- **Sync API** — blocking I/O suitable for most applications
|
||||
- **Query builder** — fluent SQL construction
|
||||
- **Vector & JSON support** — first-class multimodal types
|
||||
|
||||
## Installation
|
||||
|
||||
Add to your `Cargo.toml`:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
baradb = "1.0"
|
||||
```
|
||||
|
||||
Or from source:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/barabadb/baradadb.git
|
||||
cd clients/rust
|
||||
cargo build
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
```rust
|
||||
use baradb::Client;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = Client::connect("localhost", 9472)?;
|
||||
let result = client.query("SELECT name, age FROM users WHERE age > 18")?;
|
||||
for row in result.rows() {
|
||||
println!("{:?}", row);
|
||||
}
|
||||
client.close();
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Parameterized Queries
|
||||
|
||||
```rust
|
||||
use baradb::{Client, WireValue};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = Client::connect("localhost", 9472)?;
|
||||
let result = client.query_params(
|
||||
"SELECT * FROM users WHERE age > $1 AND country = $2",
|
||||
&[WireValue::Int64(18), WireValue::String("BG".to_string())],
|
||||
)?;
|
||||
for row in result.rows() {
|
||||
println!("{:?}", row);
|
||||
}
|
||||
client.close();
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Query Builder
|
||||
|
||||
```rust
|
||||
use baradb::Client;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = Client::connect("localhost", 9472)?;
|
||||
let result = baradb::QueryBuilder::new(&mut client)
|
||||
.select(&["name", "email"])
|
||||
.from("users")
|
||||
.where_clause("active = true")
|
||||
.order_by("name", "ASC")
|
||||
.limit(10)
|
||||
.exec()?;
|
||||
for row in result.rows() {
|
||||
println!("{:?}", row);
|
||||
}
|
||||
client.close();
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
### Vector Search
|
||||
|
||||
```rust
|
||||
use baradb::{Client, WireValue};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut client = Client::connect("localhost", 9472)?;
|
||||
let result = client.query_params(
|
||||
"SELECT id, name FROM products ORDER BY embedding <-> $1 LIMIT 5",
|
||||
&[WireValue::Vector(vec![0.1, 0.2, 0.3])],
|
||||
)?;
|
||||
client.close();
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
Unit tests (no server):
|
||||
|
||||
```bash
|
||||
cargo test --lib
|
||||
```
|
||||
|
||||
Integration tests (requires server on `localhost:9472`):
|
||||
|
||||
```bash
|
||||
# Start server
|
||||
docker run -d -p 9472:9472 baradb:latest
|
||||
|
||||
# Run all tests
|
||||
cargo test
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `Client::connect(host, port)`
|
||||
|
||||
Creates a new client connected to the given host and port.
|
||||
|
||||
### Methods
|
||||
|
||||
- `query(sql) -> Result<QueryResult>` — execute SELECT-like query
|
||||
- `query_params(sql, params) -> Result<QueryResult>` — parameterized query
|
||||
- `execute(sql) -> Result<usize>` — execute DDL/DML, returns affected rows
|
||||
- `auth(token) -> Result<()>` — JWT authentication
|
||||
- `ping() -> Result<bool>` — health check
|
||||
- `close()` — close connection
|
||||
|
||||
## License
|
||||
|
||||
Apache-2.0
|
||||
@@ -0,0 +1,127 @@
|
||||
// BaraDB Rust Client — Basic Examples
|
||||
// Make sure BaraDB is running on localhost:9472.
|
||||
|
||||
use baradb::{Client, QueryBuilder, WireValue};
|
||||
|
||||
fn example_connection() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Connection ===");
|
||||
let mut client = Client::connect("127.0.0.1", 9472)?;
|
||||
println!("Connected: {}", client.is_connected());
|
||||
println!("Ping: {}", client.ping()?);
|
||||
client.close();
|
||||
println!("Connected after close: {}", client.is_connected());
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn example_simple_query() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Simple Query ===");
|
||||
let mut client = Client::connect("127.0.0.1", 9472)?;
|
||||
let result = client.query("SELECT 42 as answer, 'BaraDB' as db")?;
|
||||
println!("Columns: {:?}", result.columns());
|
||||
println!("Row count: {}", result.row_count());
|
||||
for row in result.rows() {
|
||||
println!(" {:?}", row);
|
||||
}
|
||||
client.close();
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn example_parameterized_query() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Parameterized Query ===");
|
||||
let mut client = Client::connect("127.0.0.1", 9472)?;
|
||||
let result = client.query_params(
|
||||
"SELECT $1 as num, $2 as txt, $3 as flag",
|
||||
&[
|
||||
WireValue::Int64(123),
|
||||
WireValue::String("hello world".to_string()),
|
||||
WireValue::Bool(true),
|
||||
],
|
||||
)?;
|
||||
for row in result.rows() {
|
||||
println!(" {:?}", row);
|
||||
}
|
||||
client.close();
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn example_query_builder() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Query Builder ===");
|
||||
let mut client = Client::connect("127.0.0.1", 9472)?;
|
||||
let sql = QueryBuilder::new(&mut client)
|
||||
.select(&["id", "name"])
|
||||
.from("users")
|
||||
.where_clause("active = true")
|
||||
.order_by("name", "ASC")
|
||||
.limit(5)
|
||||
.build();
|
||||
println!("Generated SQL: {}", sql);
|
||||
client.close();
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn example_vector() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Vector Value ===");
|
||||
let mut client = Client::connect("127.0.0.1", 9472)?;
|
||||
let result = client.query_params(
|
||||
"SELECT $1 as embedding",
|
||||
&[WireValue::Vector(vec![0.1, 0.2, 0.3, 0.4])],
|
||||
)?;
|
||||
for row in result.rows() {
|
||||
println!(" {:?}", row);
|
||||
}
|
||||
client.close();
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn example_ddl_dml() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== DDL & DML ===");
|
||||
let mut client = Client::connect("127.0.0.1", 9472)?;
|
||||
|
||||
let _ = client.execute("DROP TABLE IF EXISTS demo_products");
|
||||
|
||||
client.execute(
|
||||
"CREATE TABLE demo_products (id INT PRIMARY KEY, name STRING, price FLOAT)",
|
||||
)?;
|
||||
let affected = client.execute(
|
||||
"INSERT INTO demo_products (id, name, price) VALUES (1, 'Widget', 9.99)",
|
||||
)?;
|
||||
println!("Insert affected rows: {}", affected);
|
||||
|
||||
let result = client.query("SELECT * FROM demo_products")?;
|
||||
println!("Select returned {} row(s)", result.row_count());
|
||||
for row in result.rows() {
|
||||
println!(" {:?}", row);
|
||||
}
|
||||
|
||||
client.execute("DROP TABLE demo_products")?;
|
||||
println!("Table dropped");
|
||||
client.close();
|
||||
println!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("BaraDB Rust Client Examples");
|
||||
println!("Make sure BaraDB is running on localhost:9472");
|
||||
println!();
|
||||
|
||||
let examples: Vec<fn() -> Result<(), Box<dyn std::error::Error>>> = vec![
|
||||
example_connection,
|
||||
example_simple_query,
|
||||
example_parameterized_query,
|
||||
example_query_builder,
|
||||
example_vector,
|
||||
example_ddl_dml,
|
||||
];
|
||||
|
||||
for example in examples {
|
||||
if let Err(e) = example() {
|
||||
eprintln!("ERROR: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
use baradb::Client;
|
||||
|
||||
fn main() {
|
||||
let mut client = Client::connect("localhost", 9472).unwrap();
|
||||
println!("Connected: {}", client.is_connected());
|
||||
match client.ping() {
|
||||
Ok(v) => println!("Ping: {}", v),
|
||||
Err(e) => println!("Ping error: {}", e),
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
@@ -242,6 +242,7 @@ impl Client {
|
||||
pub fn connect_with_config(config: Config) -> Result<Self, Box<dyn std::error::Error>> {
|
||||
let addr = format!("{}:{}", config.host, config.port);
|
||||
let stream = TcpStream::connect(&addr)?;
|
||||
stream.set_nodelay(true)?;
|
||||
Ok(Client {
|
||||
config,
|
||||
stream,
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
// BaraDB Rust Client — Integration Tests
|
||||
// Requires a running BaraDB server on localhost:9472.
|
||||
|
||||
use std::net::TcpStream;
|
||||
use std::time::Duration;
|
||||
|
||||
use baradb::{Client, QueryBuilder, WireValue};
|
||||
|
||||
const HOST: &str = "127.0.0.1";
|
||||
const PORT: u16 = 9472;
|
||||
|
||||
fn server_available() -> bool {
|
||||
TcpStream::connect((HOST, PORT)).is_ok()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connect_and_close() {
|
||||
if !server_available() {
|
||||
return;
|
||||
}
|
||||
let mut client = Client::connect(HOST, PORT).unwrap();
|
||||
assert!(client.is_connected());
|
||||
client.close();
|
||||
assert!(!client.is_connected());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ping() {
|
||||
if !server_available() {
|
||||
return;
|
||||
}
|
||||
let mut client = Client::connect(HOST, PORT).unwrap();
|
||||
assert!(client.ping().unwrap());
|
||||
client.close();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_simple_select() {
|
||||
if !server_available() {
|
||||
return;
|
||||
}
|
||||
let mut client = Client::connect(HOST, PORT).unwrap();
|
||||
let result = client.query("SELECT 1 as one").unwrap();
|
||||
assert!(result.row_count() >= 0);
|
||||
client.close();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parameterized_query() {
|
||||
if !server_available() {
|
||||
return;
|
||||
}
|
||||
let mut client = Client::connect(HOST, PORT).unwrap();
|
||||
let result = client
|
||||
.query_params(
|
||||
"SELECT $1 as num, $2 as txt",
|
||||
&[WireValue::Int64(42), WireValue::String("hello".to_string())],
|
||||
)
|
||||
.unwrap();
|
||||
assert!(result.row_count() >= 0);
|
||||
client.close();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_create_table_insert_select_drop() {
|
||||
if !server_available() {
|
||||
return;
|
||||
}
|
||||
let mut client = Client::connect(HOST, PORT).unwrap();
|
||||
|
||||
let _ = client.execute("DROP TABLE IF EXISTS rust_test_users");
|
||||
client
|
||||
.execute("CREATE TABLE rust_test_users (id INT PRIMARY KEY, name STRING, age INT)")
|
||||
.unwrap();
|
||||
let affected = client
|
||||
.execute("INSERT INTO rust_test_users (id, name, age) VALUES (1, 'Alice', 30)")
|
||||
.unwrap();
|
||||
assert!(affected >= 0);
|
||||
|
||||
let result = client
|
||||
.query("SELECT name, age FROM rust_test_users WHERE id = 1")
|
||||
.unwrap();
|
||||
assert_eq!(result.row_count(), 1);
|
||||
|
||||
client.execute("DROP TABLE rust_test_users").unwrap();
|
||||
client.close();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_query_builder_exec() {
|
||||
if !server_available() {
|
||||
return;
|
||||
}
|
||||
let mut client = Client::connect(HOST, PORT).unwrap();
|
||||
|
||||
let _ = client.execute("DROP TABLE IF EXISTS rust_test_products");
|
||||
client
|
||||
.execute("CREATE TABLE rust_test_products (id INT PRIMARY KEY, name STRING, price FLOAT)")
|
||||
.unwrap();
|
||||
client
|
||||
.execute("INSERT INTO rust_test_products (id, name, price) VALUES (1, 'Widget', 9.99)")
|
||||
.unwrap();
|
||||
|
||||
let result = QueryBuilder::new(&mut client)
|
||||
.select(&["name", "price"])
|
||||
.from("rust_test_products")
|
||||
.where_clause("id = 1")
|
||||
.exec()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(result.row_count(), 1);
|
||||
|
||||
client.execute("DROP TABLE rust_test_products").unwrap();
|
||||
client.close();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auth_dummy_token() {
|
||||
if !server_available() {
|
||||
return;
|
||||
}
|
||||
let mut client = Client::connect(HOST, PORT).unwrap();
|
||||
let res = client.auth("dummy-token-for-testing");
|
||||
// Dev server may accept or reject — both are fine
|
||||
match res {
|
||||
Ok(()) => {}
|
||||
Err(e) => {
|
||||
let msg = e.to_string();
|
||||
assert!(msg.contains("Auth") || msg.to_lowercase().contains("error"));
|
||||
}
|
||||
}
|
||||
client.close();
|
||||
}
|
||||
Reference in New Issue
Block a user