Update documentation and clients for v1.1.0
CI / test (push) Has been cancelled
CI / verify (push) Has been cancelled
Clients CI / build-server (push) Has been cancelled
Clients CI / test-python (push) Has been cancelled
Clients CI / test-javascript (push) Has been cancelled
Clients CI / test-nim (push) Has been cancelled
Clients CI / test-rust (push) Has been cancelled

Documentation updates:
- Fix v0.1.0 → v1.1.0 version numbers in en, ru, fa, zh docs
- Add missing Window Functions, Multi-Tenant ERP, Supported Keywords sections
  to ru, fa, zh baraql.md (~105 lines each)
- Expand Turkish and Arabic baraql.md (110 → 268 lines)
- Expand Turkish and Arabic installation.md (62 → 307 lines)
- Add new Bulgarian documentation files (18 new files)

Client updates:
- Python: Full async/await rewrite with asyncio, request queueing
- Rust: Full async/await rewrite with tokio, async examples
- Nim: Update README to v1.1.0
- All clients now support async patterns consistently
This commit is contained in:
2026-05-14 23:05:47 +03:00
parent f7d4961125
commit c55d3080cf
48 changed files with 5792 additions and 544 deletions
+4 -3
View File
@@ -3,9 +3,9 @@ name = "baradb"
version = "1.1.0"
edition = "2021"
authors = ["BaraDB Team <team@baradb.dev>"]
description = "Official Rust client for BaraDB — binary protocol client"
description = "Official async Rust client for BaraDB — binary protocol client"
license = "Apache-2.0"
repository = "https://github.com/barabadb/baradadb"
repository = "https://github.com/katehonz/barabaDB"
documentation = "https://docs.baradb.dev"
readme = "README.md"
keywords = ["database", "baradb", "multimodal", "client", "wire-protocol"]
@@ -13,9 +13,10 @@ categories = ["database", "network-programming"]
rust-version = "1.70"
[dependencies]
tokio = { version = "1.35", features = ["full"] }
[dev-dependencies]
[[example]]
name = "basic"
path = "examples/basic.rs"
path = "examples/basic.rs"
+42 -36
View File
@@ -1,13 +1,13 @@
# BaraDB Rust Client
# BaraDB Async Rust Client
Official Rust client for **BaraDB** — a multimodal database engine written in Nim.
Official async 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
- **Async/await** — fully non-blocking with Tokio runtime
- **Binary wire protocol** — fast TCP communication
- **Query builder** — fluent SQL construction
- **Parameterized queries** — safe from SQL injection
- **Vector & JSON support** — first-class multimodal types
## Installation
@@ -16,13 +16,14 @@ Add to your `Cargo.toml`:
```toml
[dependencies]
baradb = "1.0"
baradb = "1.1"
tokio = { version = "1.35", features = ["full"] }
```
Or from source:
```bash
git clone https://github.com/barabadb/baradadb.git
git clone https://github.com/katehonz/barabaDB.git
cd clients/rust
cargo build
```
@@ -32,13 +33,14 @@ cargo build
```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")?;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut client = Client::connect("localhost", 9472).await?;
let result = client.query("SELECT name, age FROM users WHERE age > 18").await?;
for row in result.rows() {
println!("{:?}", row);
}
client.close();
client.close().await;
Ok(())
}
```
@@ -48,16 +50,17 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
```rust
use baradb::{Client, WireValue};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect("localhost", 9472)?;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut client = Client::connect("localhost", 9472).await?;
let result = client.query_params(
"SELECT * FROM users WHERE age > $1 AND country = $2",
&[WireValue::Int64(18), WireValue::String("BG".to_string())],
)?;
).await?;
for row in result.rows() {
println!("{:?}", row);
}
client.close();
client.close().await;
Ok(())
}
```
@@ -65,21 +68,23 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
### Query Builder
```rust
use baradb::Client;
use baradb::{Client, QueryBuilder};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect("localhost", 9472)?;
let result = baradb::QueryBuilder::new(&mut client)
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut client = Client::connect("localhost", 9472).await?;
let result = QueryBuilder::new(&mut client)
.select(&["name", "email"])
.from("users")
.where_clause("active = true")
.order_by("name", "ASC")
.limit(10)
.exec()?;
.exec()
.await?;
for row in result.rows() {
println!("{:?}", row);
}
client.close();
client.close().await;
Ok(())
}
```
@@ -89,13 +94,14 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
```rust
use baradb::{Client, WireValue};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = Client::connect("localhost", 9472)?;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut client = Client::connect("localhost", 9472).await?;
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();
).await?;
client.close().await;
Ok(())
}
```
@@ -112,7 +118,7 @@ Integration tests (requires server on `localhost:9472`):
```bash
# Start server
docker run -d -p 9472:9472 baradb:latest
docker run -d -p 9472:9472 barabadb:latest
# Run all tests
cargo test
@@ -120,19 +126,19 @@ cargo test
## API Reference
### `Client::connect(host, port)`
### `Client::connect(host, port) -> Result<Client>`
Creates a new client connected to the given host and port.
Creates a new async client connected to the given host and port.
### Methods
### Methods (all async)
- `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
- `await client.query(sql) -> Result<QueryResult>` — execute SELECT-like query
- `await client.query_params(sql, params) -> Result<QueryResult>` — parameterized query
- `await client.execute(sql) -> Result<usize>` — execute DDL/DML, returns affected rows
- `await client.auth(token) -> Result<()>` — JWT authentication
- `await client.ping() -> Result<bool>` — health check
- `await client.close()` — close connection
## License
Apache-2.0
Apache-2.0
+59 -49
View File
@@ -3,34 +3,65 @@
use baradb::{Client, QueryBuilder, WireValue};
fn example_connection() -> Result<(), Box<dyn std::error::Error>> {
#[tokio::main]
async fn main() {
println!("BaraDB Rust Client Examples");
println!("Make sure BaraDB is running on localhost:9472");
println!();
if let Err(e) = example_connection().await {
eprintln!("ERROR: {}", e);
}
if let Err(e) = example_simple_query().await {
eprintln!("ERROR: {}", e);
}
if let Err(e) = example_parameterized_query().await {
eprintln!("ERROR: {}", e);
}
if let Err(e) = example_query_builder().await {
eprintln!("ERROR: {}", e);
}
if let Err(e) = example_vector().await {
eprintln!("ERROR: {}", e);
}
if let Err(e) = example_ddl_dml().await {
eprintln!("ERROR: {}", e);
}
}
async fn example_connection() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("=== Connection ===");
let mut client = Client::connect("127.0.0.1", 9472)?;
let mut client = Client::connect("127.0.0.1", 9472).await?;
println!("Connected: {}", client.is_connected());
println!("Ping: {}", client.ping()?);
client.close();
println!("Ping: {}", client.ping().await?);
client.close().await;
println!("Connected after close: {}", client.is_connected());
println!();
Ok(())
}
fn example_simple_query() -> Result<(), Box<dyn std::error::Error>> {
async fn example_simple_query() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("=== Simple Query ===");
let mut client = Client::connect("127.0.0.1", 9472)?;
let result = client.query("SELECT 42 as answer, 'BaraDB' as db")?;
let mut client = Client::connect("127.0.0.1", 9472).await?;
let result = client.query("SELECT 42 as answer, 'BaraDB' as db").await?;
println!("Columns: {:?}", result.columns());
println!("Row count: {}", result.row_count());
for row in result.rows() {
println!(" {:?}", row);
}
client.close();
client.close().await;
println!();
Ok(())
}
fn example_parameterized_query() -> Result<(), Box<dyn std::error::Error>> {
async fn example_parameterized_query() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("=== Parameterized Query ===");
let mut client = Client::connect("127.0.0.1", 9472)?;
let mut client = Client::connect("127.0.0.1", 9472).await?;
let result = client.query_params(
"SELECT $1 as num, $2 as txt, $3 as flag",
&[
@@ -38,18 +69,18 @@ fn example_parameterized_query() -> Result<(), Box<dyn std::error::Error>> {
WireValue::String("hello world".to_string()),
WireValue::Bool(true),
],
)?;
).await?;
for row in result.rows() {
println!(" {:?}", row);
}
client.close();
client.close().await;
println!();
Ok(())
}
fn example_query_builder() -> Result<(), Box<dyn std::error::Error>> {
async fn example_query_builder() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("=== Query Builder ===");
let mut client = Client::connect("127.0.0.1", 9472)?;
let mut client = Client::connect("127.0.0.1", 9472).await?;
let sql = QueryBuilder::new(&mut client)
.select(&["id", "name"])
.from("users")
@@ -58,70 +89,49 @@ fn example_query_builder() -> Result<(), Box<dyn std::error::Error>> {
.limit(5)
.build();
println!("Generated SQL: {}", sql);
client.close();
client.close().await;
println!();
Ok(())
}
fn example_vector() -> Result<(), Box<dyn std::error::Error>> {
async fn example_vector() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("=== Vector Value ===");
let mut client = Client::connect("127.0.0.1", 9472)?;
let mut client = Client::connect("127.0.0.1", 9472).await?;
let result = client.query_params(
"SELECT $1 as embedding",
&[WireValue::Vector(vec![0.1, 0.2, 0.3, 0.4])],
)?;
).await?;
for row in result.rows() {
println!(" {:?}", row);
}
client.close();
client.close().await;
println!();
Ok(())
}
fn example_ddl_dml() -> Result<(), Box<dyn std::error::Error>> {
async fn example_ddl_dml() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
println!("=== DDL & DML ===");
let mut client = Client::connect("127.0.0.1", 9472)?;
let mut client = Client::connect("127.0.0.1", 9472).await?;
let _ = client.execute("DROP TABLE IF EXISTS demo_products");
let _ = client.execute("DROP TABLE IF EXISTS demo_products").await;
client.execute(
"CREATE TABLE demo_products (id INT PRIMARY KEY, name STRING, price FLOAT)",
)?;
).await?;
let affected = client.execute(
"INSERT INTO demo_products (id, name, price) VALUES (1, 'Widget', 9.99)",
)?;
).await?;
println!("Insert affected rows: {}", affected);
let result = client.query("SELECT * FROM demo_products")?;
let result = client.query("SELECT * FROM demo_products").await?;
println!("Select returned {} row(s)", result.row_count());
for row in result.rows() {
println!(" {:?}", row);
}
client.execute("DROP TABLE demo_products")?;
client.execute("DROP TABLE demo_products").await?;
println!("Table dropped");
client.close();
client.close().await;
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);
}
}
}
}
+110 -75
View File
@@ -1,36 +1,42 @@
//! BaraDB Rust Client
//! BaraDB Async Rust Client
//!
//! Binary protocol client for BaraDB database.
//! Zero external dependencies — uses only `std`.
//! Async binary protocol client for BaraDB database.
//! Uses Tokio for async I/O operations.
//!
//! # Example
//! ```no_run
//! use baradb::{Client, WireValue};
//!
//! let mut client = Client::connect("localhost", 9472).unwrap();
//! let result = client.query("SELECT name FROM users WHERE age > 18").unwrap();
//! for row in result.rows() {
//! if let Some(WireValue::String(name)) = row.get("name") {
//! println!("{}", name);
//! #[tokio::main]
//! async fn main() {
//! let mut client = Client::connect("localhost", 9472).await.unwrap();
//! let result = client.query("SELECT name FROM users WHERE age > 18").await.unwrap();
//! for row in result.rows() {
//! if let Some(WireValue::String(name)) = row.get("name") {
//! println!("{}", name);
//! }
//! }
//! client.close().await;
//! }
//! client.close();
//! ```
//!
//! # Parameterized Queries
//! ```no_run
//! use baradb::{Client, WireValue};
//!
//! let mut client = Client::connect("localhost", 9472).unwrap();
//! let result = client.query_params(
//! "SELECT * FROM users WHERE age > $1",
//! &[WireValue::Int64(18)],
//! ).unwrap();
//! #[tokio::main]
//! async fn main() {
//! let mut client = Client::connect("localhost", 9472).await.unwrap();
//! let result = client.query_params(
//! "SELECT * FROM users WHERE age > $1",
//! &[WireValue::Int64(18)],
//! ).await.unwrap();
//! }
//! ```
use std::collections::HashMap;
use std::io::{Read, Write};
use std::net::TcpStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
// Client message kinds
const MK_CLIENT_HANDSHAKE: u32 = 0x01;
@@ -221,43 +227,41 @@ impl QueryResult {
}
}
/// BaraDB client
/// BaraDB async client
pub struct Client {
config: Config,
stream: TcpStream,
connected: bool,
request_id: u32,
read_buf: Vec<u8>,
}
impl Client {
pub fn connect(host: &str, port: u16) -> Result<Self, Box<dyn std::error::Error>> {
let config = Config {
host: host.to_string(),
port,
..Default::default()
};
Self::connect_with_config(config)
}
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)?;
/// Connect to a BaraDB server
pub async fn connect(host: &str, port: u16) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
let addr = format!("{}:{}", host, port);
let stream = TcpStream::connect(&addr).await?;
Ok(Client {
config,
stream,
connected: true,
request_id: 0,
read_buf: Vec::new(),
})
}
pub fn close(&mut self) {
/// Connect with custom configuration
pub async fn connect_with_config(config: Config) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
Self::connect(&config.host, config.port).await
}
/// Close the connection
pub async fn close(&mut self) {
if self.connected {
let _ = self.send_close();
let _ = self.send_close().await;
}
self.connected = false;
}
/// Check if connected
pub fn is_connected(&self) -> bool {
self.connected
}
@@ -267,27 +271,39 @@ impl Client {
self.request_id
}
fn send_close(&mut self) -> Result<(), Box<dyn std::error::Error>> {
async fn send_close(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let msg = build_message(MK_CLOSE, self.next_id(), &[]);
self.stream.write_all(&msg)?;
self.stream.write_all(&msg).await?;
Ok(())
}
fn read_header(&mut self) -> Result<(u32, u32, u32), Box<dyn std::error::Error>> {
let mut header = [0u8; 12];
self.stream.read_exact(&mut header)?;
async fn read_exact(&mut self, mut n: usize) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
let mut buf = vec![0u8; n];
let mut pos = 0;
while pos < n {
let read = self.stream.read(&mut buf[pos..]).await?;
if read == 0 {
return Err("Connection closed".into());
}
pos += read;
}
Ok(buf)
}
async fn read_header(&mut self) -> Result<(u32, u32, u32), Box<dyn std::error::Error + Send + Sync>> {
let header = self.read_exact(12).await?;
let kind = u32::from_be_bytes([header[0], header[1], header[2], header[3]]);
let length = u32::from_be_bytes([header[4], header[5], header[6], header[7]]);
let req_id = u32::from_be_bytes([header[8], header[9], header[10], header[11]]);
Ok((kind, length, req_id))
}
fn read_payload(&mut self, length: u32) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
let mut payload = vec![0u8; length as usize];
async fn read_payload(&mut self, length: u32) -> Result<Vec<u8>, Box<dyn std::error::Error + Send + Sync>> {
if length > 0 {
self.stream.read_exact(&mut payload)?;
self.read_exact(length as usize).await
} else {
Ok(vec![])
}
Ok(payload)
}
fn read_error_message(payload: &[u8]) -> String {
@@ -302,26 +318,26 @@ impl Client {
"Query error".to_string()
}
fn read_data_response(&mut self, payload: &[u8]) -> Result<QueryResult, Box<dyn std::error::Error>> {
async fn read_data_response(&mut self, payload: &[u8]) -> Result<QueryResult, Box<dyn std::error::Error + Send + Sync>> {
let mut pos = 0usize;
let col_count = read_u32(payload, &mut pos) as usize;
let col_count = read_u32(payload, &mut pos);
let mut columns = Vec::with_capacity(col_count);
let mut columns = Vec::with_capacity(col_count as usize);
for _ in 0..col_count {
columns.push(read_string(payload, &mut pos));
}
let mut col_types = Vec::with_capacity(col_count);
let mut col_types = Vec::with_capacity(col_count as usize);
for _ in 0..col_count {
col_types.push(payload[pos]);
pos += 1;
}
let row_count = read_u32(payload, &mut pos) as usize;
let mut rows = Vec::with_capacity(row_count);
let row_count = read_u32(payload, &mut pos);
let mut rows = Vec::with_capacity(row_count as usize);
for _ in 0..row_count {
let mut row = HashMap::new();
for c in 0..col_count {
for c in 0..col_count as usize {
let val = read_wire_value(payload, &mut pos);
row.insert(columns[c].clone(), val);
}
@@ -329,9 +345,9 @@ impl Client {
}
let mut affected = 0usize;
let (comp_kind, comp_len, _) = self.read_header()?;
let (comp_kind, comp_len, _) = self.read_header().await?;
if comp_kind == MK_COMPLETE {
let comp_payload = self.read_payload(comp_len)?;
let comp_payload = self.read_payload(comp_len).await?;
if comp_payload.len() >= 4 {
affected = u32::from_be_bytes([comp_payload[0], comp_payload[1], comp_payload[2], comp_payload[3]]) as usize;
}
@@ -340,44 +356,47 @@ impl Client {
Ok(QueryResult { columns, column_types: col_types, rows, affected_rows: affected })
}
pub fn auth(&mut self, token: &str) -> Result<(), Box<dyn std::error::Error>> {
/// Authenticate with JWT token
pub async fn auth(&mut self, token: &str) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
if !self.connected {
return Err("Not connected".into());
}
let payload = encode_string(token);
let msg = build_message(MK_AUTH, self.next_id(), &payload);
self.stream.write_all(&msg)?;
self.stream.write_all(&msg).await?;
let (kind, length, _) = self.read_header()?;
let (kind, length, _) = self.read_header().await?;
match kind {
MK_AUTH_OK => Ok(()),
MK_ERROR => {
let p = self.read_payload(length)?;
let p = self.read_payload(length).await?;
Err(Self::read_error_message(&p).into())
}
_ => Err(format!("Unexpected auth response: 0x{:02x}", kind).into()),
}
}
pub fn ping(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
/// Ping the server
pub async fn ping(&mut self) -> Result<bool, Box<dyn std::error::Error + Send + Sync>> {
if !self.connected {
return Err("Not connected".into());
}
let msg = build_message(MK_PING, self.next_id(), &[]);
self.stream.write_all(&msg)?;
self.stream.write_all(&msg).await?;
let (kind, length, _) = self.read_header()?;
let (kind, length, _) = self.read_header().await?;
match kind {
MK_PONG => Ok(true),
MK_ERROR => {
let p = self.read_payload(length)?;
let p = self.read_payload(length).await?;
Err(Self::read_error_message(&p).into())
}
_ => Ok(false),
}
}
pub fn query(&mut self, sql: &str) -> Result<QueryResult, Box<dyn std::error::Error>> {
/// Execute a query
pub async fn query(&mut self, sql: &str) -> Result<QueryResult, Box<dyn std::error::Error + Send + Sync>> {
if !self.connected {
return Err("Not connected".into());
}
@@ -386,14 +405,14 @@ impl Client {
payload.push(0x00); // ResultFormat::BINARY
let msg = build_message(MK_QUERY, self.next_id(), &payload);
self.stream.write_all(&msg)?;
self.stream.write_all(&msg).await?;
let (kind, length, _) = self.read_header()?;
let resp_payload = self.read_payload(length)?;
let (kind, length, _) = self.read_header().await?;
let resp_payload = self.read_payload(length).await?;
match kind {
MK_READY => Ok(QueryResult { columns: vec![], column_types: vec![], rows: vec![], affected_rows: 0 }),
MK_DATA => self.read_data_response(&resp_payload),
MK_DATA => self.read_data_response(&resp_payload).await,
MK_COMPLETE => {
let affected = if resp_payload.len() >= 4 {
u32::from_be_bytes([resp_payload[0], resp_payload[1], resp_payload[2], resp_payload[3]]) as usize
@@ -405,7 +424,8 @@ impl Client {
}
}
pub fn query_params(&mut self, sql: &str, params: &[WireValue]) -> Result<QueryResult, Box<dyn std::error::Error>> {
/// Execute a parameterized query
pub async fn query_params(&mut self, sql: &str, params: &[WireValue]) -> Result<QueryResult, Box<dyn std::error::Error + Send + Sync>> {
if !self.connected {
return Err("Not connected".into());
}
@@ -418,14 +438,14 @@ impl Client {
}
let msg = build_message(MK_QUERY_PARAMS, self.next_id(), &payload);
self.stream.write_all(&msg)?;
self.stream.write_all(&msg).await?;
let (kind, length, _) = self.read_header()?;
let resp_payload = self.read_payload(length)?;
let (kind, length, _) = self.read_header().await?;
let resp_payload = self.read_payload(length).await?;
match kind {
MK_READY => Ok(QueryResult { columns: vec![], column_types: vec![], rows: vec![], affected_rows: 0 }),
MK_DATA => self.read_data_response(&resp_payload),
MK_DATA => self.read_data_response(&resp_payload).await,
MK_COMPLETE => {
let affected = if resp_payload.len() >= 4 {
u32::from_be_bytes([resp_payload[0], resp_payload[1], resp_payload[2], resp_payload[3]]) as usize
@@ -437,12 +457,14 @@ impl Client {
}
}
pub fn execute(&mut self, sql: &str) -> Result<usize, Box<dyn std::error::Error>> {
let result = self.query(sql)?;
/// Execute and return affected rows
pub async fn execute(&mut self, sql: &str) -> Result<usize, Box<dyn std::error::Error + Send + Sync>> {
let result = self.query(sql).await?;
Ok(result.affected_rows())
}
}
/// Query builder for fluent SQL construction
pub struct QueryBuilder<'a> {
client: &'a mut Client,
select_cols: Vec<String>,
@@ -457,6 +479,7 @@ pub struct QueryBuilder<'a> {
}
impl<'a> QueryBuilder<'a> {
/// Create a new query builder
pub fn new(client: &'a mut Client) -> Self {
QueryBuilder {
client,
@@ -472,56 +495,67 @@ impl<'a> QueryBuilder<'a> {
}
}
/// Add columns to SELECT
pub fn select(mut self, cols: &[&str]) -> Self {
self.select_cols.extend(cols.iter().map(|s| s.to_string()));
self
}
/// Set the FROM table
pub fn from(mut self, table: &str) -> Self {
self.from_table = table.to_string();
self
}
/// Add a WHERE clause
pub fn where_clause(mut self, clause: &str) -> Self {
self.where_clauses.push(clause.to_string());
self
}
/// Add a JOIN clause
pub fn join(mut self, table: &str, on: &str) -> Self {
self.joins.push(format!("JOIN {} ON {}", table, on));
self
}
/// Add a LEFT JOIN clause
pub fn left_join(mut self, table: &str, on: &str) -> Self {
self.joins.push(format!("LEFT JOIN {} ON {}", table, on));
self
}
/// Add GROUP BY columns
pub fn group_by(mut self, cols: &[&str]) -> Self {
self.group_by.extend(cols.iter().map(|s| s.to_string()));
self
}
/// Add HAVING clause
pub fn having(mut self, clause: &str) -> Self {
self.having = clause.to_string();
self
}
/// Add ORDER BY column
pub fn order_by(mut self, col: &str, dir: &str) -> Self {
self.order_by.push(format!("{} {}", col, dir));
self
}
/// Set LIMIT
pub fn limit(mut self, n: usize) -> Self {
self.limit = n;
self
}
/// Set OFFSET
pub fn offset(mut self, n: usize) -> Self {
self.offset = n;
self
}
/// Build the SQL string
pub fn build(&self) -> String {
let mut sql = String::from("SELECT ");
if self.select_cols.is_empty() {
@@ -555,9 +589,10 @@ impl<'a> QueryBuilder<'a> {
sql
}
pub fn exec(self) -> Result<QueryResult, Box<dyn std::error::Error>> {
/// Execute the query
pub async fn exec(self) -> Result<QueryResult, Box<dyn std::error::Error + Send + Sync>> {
let sql = self.build();
self.client.query(&sql)
self.client.query(&sql).await
}
}
@@ -675,4 +710,4 @@ fn read_wire_value(data: &[u8], pos: &mut usize) -> WireValue {
FK_JSON => WireValue::Json(read_string(data, pos)),
_ => WireValue::Null,
}
}
}