├── .gitignore ├── .gitmodules ├── Cargo.toml ├── LICENSE ├── README.md ├── build.rs └── src ├── bin └── threading.rs ├── ffi.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.o 3 | *.so 4 | *.rlib 5 | *.dll 6 | 7 | # Executables 8 | *.exe 9 | 10 | # Generated by Cargo 11 | /target/ 12 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "sophia"] 2 | path = sophia 3 | url = https://github.com/pmwkaa/sophia.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sophia" 3 | version = "0.1.0" 4 | authors = ["Michael Neumann "] 5 | build = "build.rs" 6 | 7 | [dependencies] 8 | 9 | rand = "*" 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Neumann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-sophia 2 | Rust bindings for the [Sophia][sophia] embedded database system. 3 | 4 | [sophia]: http://sphia.org/ 5 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::process::Command; 2 | use std::env; 3 | use std::fs; 4 | use std::path::Path; 5 | 6 | #[cfg(any(target_os = "dragonfly", target_os = "freebsd"))] 7 | const MAKE: &'static str = "gmake"; 8 | 9 | #[cfg(not(any(target_os = "dragonfly", target_os = "freebsd")))] 10 | const MAKE: &'static str = "make"; 11 | 12 | fn main() { 13 | let mut cmd = Command::new(MAKE); 14 | cmd.arg("static").current_dir("sophia"); 15 | cmd.status().unwrap(); 16 | 17 | let dst = env::var("OUT_DIR").unwrap(); 18 | fs::copy("sophia/libsophia.a", Path::new(&dst).join("libsophia.a")).unwrap(); 19 | println!("cargo:rustc-link-search=native={}", dst); 20 | println!("cargo:rustc-link-lib=static=sophia"); 21 | } 22 | -------------------------------------------------------------------------------- /src/bin/threading.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate sophia; 3 | extern crate rand; 4 | 5 | use std::str; 6 | use std::thread; 7 | 8 | const N_KEYS: usize = 100_000_000; 9 | const REPEAT: usize = 10_000_000; 10 | 11 | use sophia::SetGetOps; 12 | 13 | fn write_keys(env: &sophia::Env, db: &sophia::Db) { 14 | for i in 0 .. N_KEYS { 15 | let k = format!("{}", i); 16 | let s = k.as_bytes(); 17 | 18 | let obj = obj![db; key:s, val:s]; 19 | db.set(obj); 20 | } 21 | } 22 | 23 | fn read_keys(env: &sophia::Env, db: &sophia::Db) { 24 | use rand::Rng; 25 | let mut rng = rand::thread_rng(); 26 | 27 | for _ in 0 .. REPEAT { 28 | let i: usize = rng.gen(); 29 | let key = format!("{}", i % N_KEYS); 30 | 31 | let obj = obj![db; key : key.as_bytes()]; 32 | 33 | let kv = db.get(obj).unwrap(); 34 | match kv.get_value() { 35 | Some(val) => { 36 | if let Ok(val) = str::from_utf8(val) { 37 | assert!(val == key); 38 | } 39 | } 40 | None => { 41 | println!("key not found"); 42 | } 43 | } 44 | } 45 | } 46 | 47 | fn main() { 48 | let mut env = sophia::Env::new(); 49 | env.setattr("sophia.path", "./storage"); 50 | env.setattr("db", "test"); 51 | //env.setattr("db.test.compression", "lz4"); 52 | env.setintattr("db.test.mmap", 1); 53 | env.open(); 54 | 55 | let mut db = env.get_db("test").unwrap(); 56 | 57 | write_keys(&env, &db); 58 | 59 | println!("starting up..."); 60 | 61 | let mut vec = vec![]; 62 | 63 | for _ in 1..10 { 64 | let child = thread::spawn(move || { 65 | read_keys(&env, &db); 66 | }); 67 | vec.push(child); 68 | } 69 | 70 | for child in vec { 71 | child.join(); 72 | } 73 | 74 | env.destroy(); 75 | } 76 | -------------------------------------------------------------------------------- /src/ffi.rs: -------------------------------------------------------------------------------- 1 | use libc::{c_void, c_int, c_char}; 2 | 3 | pub type Voidptr = *mut c_void; 4 | 5 | #[link(name = "sophia", kind="static")] 6 | extern { 7 | pub fn sp_env() -> Voidptr; 8 | pub fn sp_object(a: Voidptr) -> Voidptr; 9 | pub fn sp_open(a: Voidptr) -> c_int; 10 | pub fn sp_drop(a: Voidptr) -> c_int; 11 | pub fn sp_destroy(a: Voidptr) -> c_int; 12 | pub fn sp_error(a: Voidptr) -> c_int; 13 | pub fn sp_asynchronous(a: Voidptr) -> Voidptr; 14 | pub fn sp_poll(a: Voidptr) -> Voidptr; 15 | pub fn sp_setobject(a: Voidptr, b: *const c_char, c: Voidptr) -> c_int; 16 | pub fn sp_setstring(a: Voidptr, b: *const c_char, c: *const c_void, d: c_int) -> c_int; 17 | pub fn sp_setint(a: Voidptr, b: *const c_char, c: i64) -> c_int; 18 | 19 | pub fn sp_getobject(a: Voidptr, b: *const u8) -> Voidptr; 20 | pub fn sp_getstring(a: Voidptr, b: *const u8, c: *mut c_int) -> Voidptr; 21 | pub fn sp_getint(a: Voidptr, b: *mut u8) -> i64; 22 | pub fn sp_set(a: Voidptr, b: Voidptr) -> c_int; 23 | pub fn sp_update(a: Voidptr, b: Voidptr) -> c_int; 24 | pub fn sp_delete(a: Voidptr, b: Voidptr) -> c_int; 25 | pub fn sp_get(a: Voidptr, b: Voidptr) -> Voidptr; 26 | pub fn sp_cursor(a: Voidptr, b: Voidptr) -> Voidptr; 27 | pub fn sp_begin(a: Voidptr) -> Voidptr; 28 | pub fn sp_prepare(a: Voidptr) -> c_int; 29 | pub fn sp_commit(a: Voidptr) -> c_int; 30 | 31 | //pub fn sp_set_kv(a: Voidptr, key: *const u8, key_sz: c_int, val: *const u8, val_sz: c_int) -> c_int; 32 | } 33 | 34 | // NOTE: `b' must be NUL terminated string. 35 | #[inline(always)] 36 | pub fn setstring(a: Voidptr, b: &[u8], c: &[u8]) -> c_int { 37 | assert!(c.len() > 0); 38 | unsafe { sp_setstring(a, b.as_ptr() as *const c_char, c.as_ptr() as *const c_void, c.len() as i32) } 39 | } 40 | 41 | #[inline(always)] 42 | pub fn setkey(o: Voidptr, key: &[u8]) -> c_int { 43 | setstring(o, "key\0".as_bytes(), key) 44 | } 45 | 46 | #[inline(always)] 47 | pub fn setvalue(o: Voidptr, value: &[u8]) -> c_int { 48 | setstring(o, "value\0".as_bytes(), value) 49 | } 50 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(libc)] 2 | 3 | extern crate libc; 4 | use std::ffi::CString; 5 | use std::slice; 6 | use std::ptr; 7 | use std::iter::Iterator; 8 | 9 | mod ffi; 10 | 11 | #[derive(Debug, Copy, Clone)] 12 | pub struct Env { 13 | env: ffi::Voidptr 14 | } 15 | 16 | #[derive(Debug, Copy, Clone)] 17 | pub struct Db { 18 | db: ffi::Voidptr 19 | } 20 | 21 | // XXX: Use Arc 22 | unsafe impl Sync for Db {} 23 | unsafe impl Send for Db {} 24 | unsafe impl Sync for Env {} 25 | unsafe impl Send for Env {} 26 | 27 | use std::marker::PhantomData; 28 | 29 | #[macro_export] 30 | macro_rules! obj { 31 | ( 32 | $db:ident ; 33 | $( 34 | $name:ident : $x:expr 35 | ),* 36 | ) => {{ 37 | let mut obj = $db.obj(); 38 | $( 39 | obj.$name($x) 40 | );* 41 | ;obj 42 | }} 43 | } 44 | 45 | 46 | #[derive(Debug)] 47 | pub struct Object<'a> { 48 | o: ffi::Voidptr, 49 | phantom: PhantomData<&'a ()>, 50 | } 51 | 52 | impl<'a> Object<'a> { 53 | // We name it attr(), to not mix it up with the set() method. 54 | pub fn attr<'b>(&'b mut self, path: &str, data: &'b[u8]) { 55 | let path = CString::new(path).unwrap(); 56 | unsafe {ffi::setstring(self.o, path.as_bytes(), data); } 57 | } 58 | 59 | // Shortcut for attr("key", ...) 60 | pub fn key<'b>(&'b mut self, data: &'b[u8]) { 61 | unsafe {ffi::setstring(self.o, "key\0".as_bytes(), data); } 62 | } 63 | 64 | // Shortcut for attr("key_b", ...) 65 | // This allows setting a secondary key. 66 | pub fn key_b<'b>(&'b mut self, data: &'b[u8]) { 67 | unsafe {ffi::setstring(self.o, "key_b\0".as_bytes(), data); } 68 | } 69 | 70 | // Shortcut for attr("value", ...) 71 | pub fn value<'b>(&'b mut self, data: &'b[u8]) { 72 | unsafe {ffi::setstring(self.o, "value\0".as_bytes(), data); } 73 | } 74 | 75 | // Shortcut for attr("val", ...) 76 | pub fn val<'b>(&'b mut self, data: &'b[u8]) { 77 | self.value(data) 78 | } 79 | 80 | // Shortcut for attr("order", ...) 81 | pub fn order<'b>(&'b mut self, data: &'b[u8]) { 82 | unsafe {ffi::setstring(self.o, "order\0".as_bytes(), data); } 83 | } 84 | 85 | // Shortcut for attr("prefix", ...) 86 | pub fn prefix<'b>(&'b mut self, data: &'b[u8]) { 87 | unsafe {ffi::setstring(self.o, "prefix\0".as_bytes(), data); } 88 | } 89 | } 90 | 91 | impl<'a> Drop for Object<'a> { 92 | fn drop(&mut self) { 93 | if !self.o.is_null() { 94 | unsafe {ffi::sp_destroy(self.o)}; 95 | } 96 | } 97 | } 98 | 99 | #[derive(Debug)] 100 | pub struct ResultObject<'a> { 101 | o: ffi::Voidptr, 102 | phantom: PhantomData<&'a ()>, 103 | } 104 | 105 | impl<'a> ResultObject<'a> { 106 | fn get_<'b>(&'b self, key: &'b [u8]) -> Option<&'b[u8]> { 107 | unsafe { 108 | let mut sz = 0; 109 | let valptr = ffi::sp_getstring(self.o, key.as_ptr(), &mut sz); 110 | 111 | // XXX: what if we stored an empty value? 112 | if valptr.is_null() { 113 | return None; 114 | } 115 | 116 | let s = slice::from_raw_parts(valptr as *const u8, sz as usize); 117 | return Some(s); 118 | } 119 | 120 | } 121 | pub fn get_value<'b>(&'b self) -> Option<&'b[u8]> { 122 | self.get_(b"value\0") 123 | } 124 | 125 | pub fn get_key<'b>(&'b self) -> Option<&'b[u8]> { 126 | self.get_(b"key\0") 127 | } 128 | 129 | pub fn get_key_b<'b>(&'b self) -> Option<&'b[u8]> { 130 | self.get_(b"key_b\0") 131 | } 132 | } 133 | 134 | impl<'a> Drop for ResultObject<'a> { 135 | fn drop(&mut self) { 136 | if !self.o.is_null() { 137 | unsafe {ffi::sp_destroy(self.o)}; 138 | } 139 | } 140 | } 141 | 142 | #[derive(Debug)] 143 | pub struct Transaction<'a> { 144 | tx: ffi::Voidptr, 145 | phantom: PhantomData<&'a ()> 146 | } 147 | 148 | impl<'a> Drop for Transaction<'a> { 149 | // Unless commit() is called, this will roll back the transaction 150 | fn drop(&mut self) { 151 | if !self.tx.is_null() { 152 | unsafe {ffi::sp_destroy(self.tx);} 153 | } 154 | } 155 | } 156 | 157 | pub struct Cursor<'a> { 158 | obj: ffi::Voidptr, 159 | phantom: PhantomData<&'a ()> 160 | } 161 | 162 | impl<'a> Drop for Cursor<'a> { 163 | fn drop(&mut self) { 164 | unsafe {ffi::sp_destroy(self.obj);} 165 | } 166 | } 167 | 168 | impl<'a> Iterator for Cursor<'a> { 169 | type Item = ResultObject<'a>; 170 | fn next(&mut self) -> Option { 171 | let res = unsafe { ffi::sp_get(self.obj, ptr::null_mut()) }; 172 | if res.is_null() { 173 | None 174 | } 175 | else { 176 | Some(ResultObject{o: res, phantom: PhantomData}) 177 | } 178 | } 179 | } 180 | 181 | pub trait SetGetOps { 182 | fn backend(&self) -> ffi::Voidptr; 183 | 184 | // Consumes the Object 185 | fn set<'a>(&self, obj: Object<'a>) { 186 | let mut obj = obj; 187 | unsafe {ffi::sp_set(self.backend(), obj.o)}; // XXX: Check error code 188 | obj.o = ptr::null_mut(); // sp_set drops it 189 | } 190 | 191 | // XXX: Make sure self.db == dbobject.db 192 | fn get<'a, 'b>(&'a self, pattern: Object<'b>) -> Option> { 193 | unsafe { 194 | let res = ffi::sp_get(self.backend(), pattern.o); 195 | if res.is_null() { 196 | return None; 197 | } 198 | let n = ResultObject { 199 | o: pattern.o, 200 | phantom: PhantomData 201 | }; 202 | let mut pattern = pattern; 203 | pattern.o = ptr::null_mut(); 204 | return Some(n); 205 | } 206 | } 207 | 208 | fn cursor<'a, 'b>(&'a self, pattern: Object<'b>) -> Cursor<'a> { 209 | unsafe { 210 | let cursor = ffi::sp_cursor(self.backend(), pattern.o); 211 | assert!(!cursor.is_null()); 212 | 213 | // pattern is freed as part of the Cursor 214 | let mut pattern = pattern; 215 | pattern.o = ptr::null_mut(); 216 | 217 | Cursor {obj: cursor, phantom: PhantomData} 218 | } 219 | } 220 | } 221 | 222 | impl<'a> Transaction<'a> { 223 | pub fn commit(mut self) -> i32 { 224 | let rc = unsafe{ffi::sp_commit(self.tx)}; 225 | self.tx = ptr::null_mut(); 226 | rc as i32 227 | } 228 | } 229 | 230 | impl<'a> SetGetOps for Transaction<'a> { 231 | fn backend(&self) -> ffi::Voidptr { self.tx } 232 | } 233 | 234 | impl Env { 235 | pub fn new() -> Env { 236 | Env {env: unsafe{ffi::sp_env()}} 237 | } 238 | 239 | pub fn destroy(self) { 240 | unsafe{ffi::sp_destroy(self.env)}; 241 | } 242 | 243 | pub fn db(&mut self, dbname: &str) { 244 | // XXX: is dbname copied? 245 | let dbname = CString::new(dbname).unwrap(); 246 | unsafe {ffi::setstring(self.env, "db\0".as_bytes(), dbname.as_bytes()) }; 247 | } 248 | 249 | pub fn open(&mut self) { 250 | unsafe {ffi::sp_open(self.env)}; 251 | } 252 | 253 | pub fn begin<'a>(&'a self) -> Transaction<'a> { 254 | let tx = unsafe{ffi::sp_begin(self.env)}; 255 | assert!(!tx.is_null()); 256 | Transaction { 257 | tx: tx, 258 | phantom: PhantomData 259 | } 260 | } 261 | 262 | pub fn get_db(&self, dbname: &str) -> Option { 263 | let dbstr = "db.".to_string() + dbname; 264 | let attr = CString::new(&dbstr[..]).unwrap(); 265 | unsafe { 266 | let obj = ffi::sp_getobject(self.env, attr.as_ptr() as *const u8); 267 | if obj.is_null() { 268 | None 269 | } 270 | else { 271 | Some(Db {db: obj}) 272 | } 273 | } 274 | } 275 | 276 | pub fn setattr(&mut self, key: &str, val: &str) { 277 | let key = CString::new(key).unwrap(); 278 | let val = CString::new(val).unwrap(); 279 | unsafe {ffi::setstring(self.env, key.as_bytes(), val.as_bytes()) }; 280 | } 281 | 282 | pub fn setintattr(&mut self, key: &str, val: i64) { 283 | let key = CString::new(key).unwrap(); 284 | unsafe {ffi::sp_setint(self.env, key.as_ptr(), val) }; 285 | } 286 | } 287 | 288 | impl SetGetOps for Db { 289 | fn backend(&self) -> ffi::Voidptr { self.db } 290 | } 291 | 292 | impl Db { 293 | pub fn obj<'a>(&'a self) -> Object<'a> { 294 | let obj = unsafe { ffi::sp_object(self.db) }; 295 | assert!(!obj.is_null()); 296 | Object{o: obj, phantom: PhantomData} 297 | } 298 | 299 | } 300 | --------------------------------------------------------------------------------