Skip to content

Commit 04a8277

Browse files
committed
Add Rust Driver
1 parent 3c64d15 commit 04a8277

File tree

12 files changed

+403
-17
lines changed

12 files changed

+403
-17
lines changed

.github/workflows/test.yml

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
name: Test
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
build:
7+
name: ${{ matrix.job.target }}
8+
runs-on: ${{ matrix.job.os }}
9+
strategy:
10+
matrix:
11+
job:
12+
- target: x86_64-unknown-linux-gnu
13+
os: ubuntu-latest
14+
15+
- target: x86_64-apple-darwin
16+
os: macos-latest
17+
18+
- target: aarch64-apple-darwin
19+
os: macos-latest
20+
21+
# TODO
22+
# - target: x86_64-pc-windows-msvc
23+
# os: windows-latest
24+
25+
steps:
26+
- uses: actions/checkout@v4
27+
with:
28+
submodules: recursive
29+
30+
- uses: actions/cache@v4
31+
with:
32+
path: |
33+
~/.cargo/bin/
34+
~/.cargo/registry/index/
35+
~/.cargo/registry/cache/
36+
~/.cargo/git/db/
37+
target/
38+
key: ${{ matrix.job.target }}
39+
40+
- name: Setup Rust
41+
run: |
42+
rustup update
43+
rustup target add ${{ matrix.job.target }}
44+
45+
- name: Cargo fmt
46+
run: |
47+
cargo fmt --all -- --check
48+
49+
- name: Cargo test
50+
run: |
51+
cargo test --target ${{ matrix.job.target }}
52+
53+
- name: Cargo run --example
54+
run: |
55+
cargo run --example basic --target ${{ matrix.job.target }}

.gitignore

+2-17
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,6 @@
1-
# Generated by Cargo
2-
# will have compiled files and executables
3-
debug/
41
target/
5-
6-
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
7-
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
82
Cargo.lock
9-
10-
# These are backup files generated by rustfmt
113
**/*.rs.bk
12-
13-
# MSVC Windows builds of rustc generate these, which store debugging information
144
*.pdb
15-
16-
# RustRover
17-
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
18-
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
19-
# and can be added to the global gitignore or merged into this file. For a more nuclear
20-
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
21-
#.idea/
5+
.DS_Store
6+
test.db/

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "crossdb"]
2+
path = crossdb
3+
url = https://github.com/crossdb-org/crossdb

Cargo.toml

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
[package]
2+
name = "crossdb"
3+
version = "0.0.1"
4+
edition = "2021"
5+
license = "MIT"
6+
description = "CrossDB Rust Driver"
7+
readme = "README.md"
8+
homepage = "hhttps://github.com/crossdb-org/crossdb-rust"
9+
repository = "https://github.com/crossdb-org/crossdb-rust.git"
10+
11+
[dependencies]
12+
thiserror = "1.0"
13+
14+
[build-dependencies]
15+
bindgen = "0.70"
16+
cc = "1.1"

README.md

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# crossdb-rs
2+
3+
```toml
4+
[dependencies]
5+
crossdb = { git = "https://github.com/crossdb-org/crossdb-rust" }
6+
```
7+
8+
```rs
9+
use crossdb::Connection;
10+
11+
fn main() {
12+
let conn = Connection::open_with_memory().unwrap();
13+
let mut rst = conn.exec("select * from system.databases;").unwrap();
14+
15+
for i in 0..rst.column_count() {
16+
println!("Column {i}: {} {}", rst.column_name(i), rst.column_type(i));
17+
}
18+
19+
while let Some(row) = (&mut rst).next() {
20+
dbg!(row);
21+
}
22+
}
23+
```

build.rs

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
use std::env;
2+
use std::path::PathBuf;
3+
4+
fn main() {
5+
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
6+
bindgen::builder()
7+
.header("crossdb/include/crossdb.h")
8+
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
9+
.generate()
10+
.unwrap()
11+
.write_to_file(out_path.join("./bindings.rs"))
12+
.unwrap();
13+
14+
let mut builder = cc::Build::new();
15+
builder
16+
.file("crossdb/src/crossdb.c")
17+
.include("crossdb/include")
18+
.flag("-fPIC")
19+
.opt_level(2)
20+
.static_flag(true)
21+
.compile("crossdb");
22+
println!("cargo:rustc-link-lib=static=crossdb");
23+
println!("cargo:rustc-link-lib=pthread");
24+
25+
println!("cargo:rerun-if-changed=crossdb/");
26+
}

crossdb

Submodule crossdb added at 53faea9

examples/basic.rs

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use crossdb::Connection;
2+
3+
fn main() {
4+
let conn = Connection::open("test.db").unwrap();
5+
let mut rst = conn.exec("select * FROM system.databases;").unwrap();
6+
7+
for i in 0..rst.column_count() {
8+
println!("Column {i}: {} {}", rst.column_name(i), rst.column_type(i));
9+
}
10+
11+
while let Some(row) = (&mut rst).next() {
12+
dbg!(row);
13+
}
14+
}

src/column.rs

+86
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
use crate::*;
2+
3+
// https://crossdb.org/client/api-c/#xdb_type_t
4+
#[derive(Debug, Clone, Copy)]
5+
pub enum ColumnType {
6+
Null,
7+
TinyInt,
8+
SmallInt,
9+
Int,
10+
BigInt,
11+
UTinyInt,
12+
USmallInt,
13+
UInt,
14+
UBigInt,
15+
Float,
16+
Double,
17+
Timestamp,
18+
Char,
19+
Binary,
20+
VChar,
21+
VBinary,
22+
Max,
23+
}
24+
25+
impl From<u32> for ColumnType {
26+
#[allow(non_upper_case_globals)]
27+
fn from(value: u32) -> Self {
28+
match value {
29+
xdb_type_t_XDB_TYPE_NULL => ColumnType::Null,
30+
xdb_type_t_XDB_TYPE_TINYINT => ColumnType::TinyInt,
31+
xdb_type_t_XDB_TYPE_SMALLINT => ColumnType::SmallInt,
32+
xdb_type_t_XDB_TYPE_INT => ColumnType::Int,
33+
xdb_type_t_XDB_TYPE_BIGINT => ColumnType::BigInt,
34+
xdb_type_t_XDB_TYPE_UTINYINT => ColumnType::UTinyInt,
35+
xdb_type_t_XDB_TYPE_USMALLINT => ColumnType::USmallInt,
36+
xdb_type_t_XDB_TYPE_UINT => ColumnType::UInt,
37+
xdb_type_t_XDB_TYPE_UBIGINT => ColumnType::UBigInt,
38+
xdb_type_t_XDB_TYPE_FLOAT => ColumnType::Float,
39+
xdb_type_t_XDB_TYPE_DOUBLE => ColumnType::Double,
40+
xdb_type_t_XDB_TYPE_TIMESTAMP => ColumnType::Timestamp,
41+
xdb_type_t_XDB_TYPE_CHAR => ColumnType::Char,
42+
xdb_type_t_XDB_TYPE_BINARY => ColumnType::Binary,
43+
xdb_type_t_XDB_TYPE_VCHAR => ColumnType::VChar,
44+
xdb_type_t_XDB_TYPE_VBINARY => ColumnType::VBinary,
45+
xdb_type_t_XDB_TYPE_MAX => ColumnType::Max,
46+
_ => unreachable!(),
47+
}
48+
}
49+
}
50+
51+
impl Display for ColumnType {
52+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53+
match self {
54+
ColumnType::Null => write!(f, "NULL"),
55+
ColumnType::TinyInt => write!(f, "TINYINT"),
56+
ColumnType::SmallInt => write!(f, "SMALLINT"),
57+
ColumnType::Int => write!(f, "INT"),
58+
ColumnType::BigInt => write!(f, "BIGINT"),
59+
ColumnType::UTinyInt => write!(f, "UTINYINT"),
60+
ColumnType::USmallInt => write!(f, "USMALLINT"),
61+
ColumnType::UInt => write!(f, "UINT"),
62+
ColumnType::UBigInt => write!(f, "UBIGINT"),
63+
ColumnType::Float => write!(f, "FLOAT"),
64+
ColumnType::Double => write!(f, "DOUBLE"),
65+
ColumnType::Timestamp => write!(f, "TIMESTAMP"),
66+
ColumnType::Char => write!(f, "CHAR"),
67+
ColumnType::Binary => write!(f, "BINARY"),
68+
ColumnType::VChar => write!(f, "VCHAR"),
69+
ColumnType::VBinary => write!(f, "VBINARY"),
70+
ColumnType::Max => write!(f, "MAX"),
71+
}
72+
}
73+
}
74+
75+
impl ColumnType {
76+
pub(crate) fn all(res: &xdb_res_t) -> Vec<Self> {
77+
let mut vec = Vec::with_capacity(res.col_count as usize);
78+
for i in 0..vec.capacity() {
79+
unsafe {
80+
let t = xdb_column_type(res.col_meta, i as u16);
81+
vec.push(Self::from(t));
82+
}
83+
}
84+
vec
85+
}
86+
}

src/error.rs

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use std::ffi::NulError;
2+
3+
pub type Result<T, E = Error> = std::result::Result<T, E>;
4+
5+
#[derive(Debug, thiserror::Error)]
6+
pub enum Error {
7+
#[error("CString error: {0}")]
8+
CString(#[from] NulError),
9+
#[error("UTF8 error: {0}")]
10+
Utf8(#[from] std::str::Utf8Error),
11+
#[error("Query error: {0}, {1}")]
12+
Query(u16, String),
13+
}

src/lib.rs

+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
#![allow(
2+
dead_code,
3+
non_snake_case,
4+
non_camel_case_types,
5+
non_upper_case_globals
6+
)]
7+
8+
mod crossdb_sys {
9+
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
10+
}
11+
12+
mod column;
13+
mod error;
14+
mod value;
15+
16+
pub use column::ColumnType;
17+
pub use error::{Error, Result};
18+
pub use value::Value;
19+
20+
use crossdb_sys::*;
21+
use std::ffi::{CStr, CString};
22+
use std::fmt::Display;
23+
24+
#[derive(Debug)]
25+
pub struct Connection {
26+
ptr: *mut xdb_conn_t,
27+
}
28+
29+
impl Drop for Connection {
30+
fn drop(&mut self) {
31+
unsafe {
32+
xdb_close(self.ptr);
33+
}
34+
}
35+
}
36+
37+
impl Connection {
38+
pub fn open<P: AsRef<str>>(path: P) -> Result<Self> {
39+
let path = CString::new(path.as_ref())?;
40+
let ptr = unsafe { xdb_open(path.as_ptr()) };
41+
Ok(Self { ptr })
42+
}
43+
44+
pub fn open_with_memory() -> Result<Self> {
45+
Self::open(":memory:")
46+
}
47+
48+
pub fn exec<S: AsRef<str>>(&self, sql: S) -> Result<ExecResult> {
49+
let sql = CString::new(sql.as_ref())?;
50+
unsafe {
51+
let ptr = xdb_exec(self.ptr, sql.as_ptr());
52+
let res = *ptr;
53+
if res.errcode as u32 != xdb_errno_e_XDB_OK {
54+
let msg = CStr::from_ptr(xdb_errmsg(ptr)).to_str()?.to_string();
55+
return Err(Error::Query(res.errcode, msg));
56+
}
57+
Ok(ExecResult {
58+
ptr,
59+
col_meta: res.col_meta,
60+
column_count: res.col_count as usize,
61+
row_count: res.row_count as usize,
62+
column_types: ColumnType::all(&res),
63+
row_index: 0,
64+
})
65+
}
66+
}
67+
}
68+
69+
#[derive(Debug)]
70+
pub struct ExecResult {
71+
ptr: *mut xdb_res_t,
72+
col_meta: u64,
73+
column_count: usize,
74+
row_count: usize,
75+
column_types: Vec<ColumnType>,
76+
row_index: usize,
77+
}
78+
79+
impl Drop for ExecResult {
80+
fn drop(&mut self) {
81+
unsafe {
82+
xdb_free_result(self.ptr);
83+
}
84+
}
85+
}
86+
87+
impl ExecResult {
88+
pub fn column_count(&self) -> usize {
89+
self.column_count
90+
}
91+
92+
pub fn row_count(&self) -> usize {
93+
self.row_count
94+
}
95+
96+
pub fn column_name<'a>(&'a self, i: usize) -> &'a str {
97+
unsafe {
98+
let name = xdb_column_name(self.col_meta, i as u16);
99+
CStr::from_ptr(name).to_str().unwrap()
100+
}
101+
}
102+
103+
pub fn column_type(&self, i: usize) -> ColumnType {
104+
self.column_types[i]
105+
}
106+
}
107+
108+
impl<'a> Iterator for &'a mut ExecResult {
109+
type Item = Vec<Value<'a>>;
110+
111+
fn next(&mut self) -> Option<Self::Item> {
112+
if self.row_count <= self.row_index {
113+
return None;
114+
}
115+
let mut values = Vec::with_capacity(self.column_count);
116+
unsafe {
117+
let y = xdb_fetch_row(self.ptr);
118+
for x in 0..self.column_count {
119+
let value = Value::from_result(self.col_meta, y, x as u16, self.column_type(x));
120+
values.push(value);
121+
}
122+
}
123+
self.row_index += 1;
124+
Some(values)
125+
}
126+
}

0 commit comments

Comments
 (0)