Skip to content

Commit d929681

Browse files
committed
Use LRU cache for SQL statement
1 parent 0bfd401 commit d929681

File tree

9 files changed

+156
-92
lines changed

9 files changed

+156
-92
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ Cargo.lock
33
**/*.rs.bk
44
*.pdb
55
.DS_Store
6-
test.db/
6+
test/

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ homepage = "hhttps://github.com/crossdb-org/crossdb-rust"
99
repository = "https://github.com/crossdb-org/crossdb-rust.git"
1010

1111
[dependencies]
12+
lru = "0.12"
1213
serde = "1.0"
1314
thiserror = "1.0"
1415

README.md

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,35 @@ crossdb = { git = "https://github.com/crossdb-org/crossdb-rust" }
66
```
77

88
```rs
9-
use crossdb::Connection;
9+
use crossdb::{Connection, Result};
1010

11-
fn main() {
12-
let conn = Connection::open_with_memory().unwrap();
13-
let mut rst = conn.exec("select * from system.databases;").unwrap();
11+
fn main() -> Result<()> {
12+
let mut conn = Connection::open_with_memory()?;
1413

15-
for (name, datatype) in rst.columns() {
14+
conn.execute("create table if not exists users(id int, name CHAR(255));")?;
15+
let stmt = conn.prepare("insert into users (id, name) values (?, ?);")?;
16+
17+
stmt.execute((1, "Alex"))?;
18+
stmt.execute((2, "Thorne"))?;
19+
stmt.execute((3, "Ryder"))?;
20+
21+
let mut query = conn.query("select * from users;")?;
22+
23+
for (name, datatype) in query.columns() {
1624
println!("Column : {} {}", name, datatype);
1725
}
1826

19-
while let Some(row) = rst.fetch_row() {
20-
dbg!(row.values());
27+
while let Some(row) = query.fetch_row() {
28+
println!("User: {}, Name: {}", row.get("id"), row.get("name"));
2129
}
30+
31+
Ok(())
2232
}
2333
```
2434

2535
## TODO
26-
* NULL value
27-
* Add more apis
28-
* Windows support
29-
* Dynamic link crossdb
30-
* use serde to serialize/deserialize
36+
37+
- NULL value
38+
- Windows support
39+
- Dynamic link crossdb
40+
- use serde to serialize/deserialize

examples/basic.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use crossdb::Connection;
22

33
fn main() {
4-
let conn = Connection::open("test.db").unwrap();
5-
let mut rst = conn.exec("select * FROM system.databases;").unwrap();
4+
let conn = Connection::open("test").unwrap();
5+
let mut query = conn.query("select * from system.databases;").unwrap();
66

7-
for (name, datatype) in rst.columns() {
7+
for (name, datatype) in query.columns() {
88
println!("Column : {} {}", name, datatype);
99
}
1010

11-
while let Some(row) = rst.fetch_row() {
11+
while let Some(row) = query.fetch_row() {
1212
dbg!(row.values());
1313
}
1414
}

src/lib.rs

Lines changed: 50 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -13,24 +13,32 @@ mod column;
1313
mod de;
1414
mod error;
1515
mod row;
16+
mod statement;
1617
mod value;
1718

1819
pub use column::{Columns, DataType};
1920
pub use error::{Error, Result};
20-
pub use row::Row;
21+
pub use params::{IntoParams, Params, Value as ParamValue};
22+
pub use row::{IntoValueIndex, Row, ValueIndex};
23+
pub use statement::Statement;
2124
pub use value::Value;
2225

2326
use crossdb_sys::*;
24-
use params::{IntoParams, Value as ParamValue};
27+
use lru::LruCache;
2528
use std::ffi::{CStr, CString};
2629
use std::fmt::Display;
30+
use std::num::NonZeroUsize;
2731
mod params;
2832

2933
#[derive(Debug)]
3034
pub struct Connection {
3135
ptr: *mut xdb_conn_t,
36+
cache: LruCache<CString, Statement>,
3237
}
3338

39+
unsafe impl Send for Connection {}
40+
unsafe impl Sync for Connection {}
41+
3442
impl Drop for Connection {
3543
fn drop(&mut self) {
3644
unsafe {
@@ -43,30 +51,29 @@ impl Connection {
4351
pub fn open<P: AsRef<str>>(path: P) -> Result<Self> {
4452
let path = CString::new(path.as_ref())?;
4553
let ptr = unsafe { xdb_open(path.as_ptr()) };
46-
Ok(Self { ptr })
54+
let cap = NonZeroUsize::new(256).unwrap();
55+
Ok(Self {
56+
ptr,
57+
cache: LruCache::new(cap),
58+
})
4759
}
4860

4961
pub fn open_with_memory() -> Result<Self> {
5062
Self::open(":memory:")
5163
}
5264

53-
pub fn exec<S: AsRef<str>>(&self, sql: S) -> Result<ExecResult> {
65+
pub fn query<S: AsRef<str>>(&self, sql: S) -> Result<Query> {
5466
let sql = CString::new(sql.as_ref())?;
5567
unsafe {
5668
let ptr = xdb_exec(self.ptr, sql.as_ptr());
57-
let res = *ptr;
58-
if res.errcode as u32 != xdb_errno_e_XDB_OK {
59-
let msg = CStr::from_ptr(xdb_errmsg(ptr)).to_str()?.to_string();
60-
return Err(Error::Query(res.errcode, msg));
61-
}
62-
Ok(ExecResult {
63-
res,
64-
ptr,
65-
columns: Columns::from_res(ptr),
66-
})
69+
Query::from_res(ptr)
6770
}
6871
}
6972

73+
pub fn execute<S: AsRef<str>>(&self, sql: S) -> Result<u64> {
74+
self.query(sql).map(|q| q.affected_rows())
75+
}
76+
7077
pub fn begin(&self) -> bool {
7178
unsafe { xdb_begin(self.ptr) == 0 }
7279
}
@@ -79,67 +86,53 @@ impl Connection {
7986
unsafe { xdb_rollback(self.ptr) == 0 }
8087
}
8188

82-
// TODO: LRU cache
83-
pub fn prepare<S: AsRef<str>>(&mut self, sql: S) -> Result<Stmt> {
84-
unsafe {
85-
let sql = CString::new(sql.as_ref())?;
86-
let ptr = xdb_stmt_prepare(self.ptr, sql.as_ptr());
87-
Ok(Stmt { ptr })
88-
}
89-
}
90-
}
91-
92-
pub struct Stmt {
93-
ptr: *mut xdb_stmt_t,
94-
}
95-
96-
impl Drop for Stmt {
97-
fn drop(&mut self) {
98-
unsafe {
99-
xdb_stmt_close(self.ptr);
100-
}
89+
pub fn prepare<S: AsRef<str>>(&mut self, sql: S) -> Result<&Statement> {
90+
let sql = CString::new(sql.as_ref())?;
91+
let sql_ptr = sql.as_ptr();
92+
let stmt = self.cache.get_or_insert(sql, || {
93+
let ptr = unsafe { xdb_stmt_prepare(self.ptr, sql_ptr) };
94+
Statement { ptr }
95+
});
96+
Ok(stmt)
10197
}
102-
}
10398

104-
impl Stmt {
105-
pub fn exec(&self, params: impl IntoParams) -> Result<ExecResult> {
106-
unsafe {
107-
let ret = xdb_clear_bindings(self.ptr);
108-
if ret != 0 {
109-
return Err(Error::ClearBindings);
110-
}
111-
params.into_params()?.bind(self.ptr)?;
112-
let ptr = xdb_stmt_exec(self.ptr);
113-
let res = *ptr;
114-
if res.errcode as u32 != xdb_errno_e_XDB_OK {
115-
let msg = CStr::from_ptr(xdb_errmsg(ptr)).to_str()?.to_string();
116-
return Err(Error::Query(res.errcode, msg));
117-
}
118-
Ok(ExecResult {
119-
res,
120-
ptr,
121-
columns: Columns::from_res(ptr),
122-
})
123-
}
99+
pub fn clear_statement_cache(&mut self) {
100+
self.cache.clear();
124101
}
125102
}
126103

127104
#[derive(Debug)]
128-
pub struct ExecResult {
105+
pub struct Query {
129106
res: xdb_res_t,
130107
ptr: *mut xdb_res_t,
131108
columns: Columns,
132109
}
133110

134-
impl Drop for ExecResult {
111+
impl Drop for Query {
135112
fn drop(&mut self) {
136113
unsafe {
137114
xdb_free_result(self.ptr);
138115
}
139116
}
140117
}
141118

142-
impl ExecResult {
119+
unsafe impl Send for Query {}
120+
unsafe impl Sync for Query {}
121+
122+
impl Query {
123+
pub(crate) unsafe fn from_res(ptr: *mut xdb_res_t) -> Result<Self> {
124+
let res = *ptr;
125+
if res.errcode as u32 != xdb_errno_e_XDB_OK {
126+
let msg = CStr::from_ptr(xdb_errmsg(ptr)).to_str()?.to_string();
127+
return Err(Error::Query(res.errcode, msg));
128+
}
129+
Ok(Self {
130+
res,
131+
ptr,
132+
columns: Columns::from_res(ptr),
133+
})
134+
}
135+
143136
pub fn column_count(&self) -> usize {
144137
self.res.col_count as usize
145138
}

src/params.rs

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,23 @@ impl IntoValue for Value {
4646
}
4747

4848
pub enum Params {
49-
None,
49+
Empty,
5050
Positional(Vec<Value>),
5151
}
5252

5353
impl Params {
54-
pub(crate) unsafe fn bind(self, ptr: *mut xdb_stmt_t) -> Result<()> {
55-
if let Params::Positional(params) = self {
56-
for (i, p) in params.into_iter().enumerate() {
57-
let i = i as u16 + 1;
58-
let ret = match p {
59-
ParamValue::Int(v) => xdb_bind_int(ptr, i, v),
60-
ParamValue::Int64(v) => xdb_bind_int64(ptr, i, v),
61-
ParamValue::Float(v) => xdb_bind_float(ptr, i, v),
62-
ParamValue::Double(v) => xdb_bind_double(ptr, i, v),
63-
ParamValue::String(v) => xdb_bind_str(ptr, i, CString::new(v)?.as_ptr()),
64-
};
65-
if ret != 0 {
66-
return Err(Error::BindParams);
67-
}
54+
pub(crate) unsafe fn bind(ptr: *mut xdb_stmt_t, params: Vec<Value>) -> Result<()> {
55+
for (i, p) in params.into_iter().enumerate() {
56+
let i = i as u16 + 1;
57+
let ret = match p {
58+
ParamValue::Int(v) => xdb_bind_int(ptr, i, v),
59+
ParamValue::Int64(v) => xdb_bind_int64(ptr, i, v),
60+
ParamValue::Float(v) => xdb_bind_float(ptr, i, v),
61+
ParamValue::Double(v) => xdb_bind_double(ptr, i, v),
62+
ParamValue::String(v) => xdb_bind_str(ptr, i, CString::new(v)?.as_ptr()),
63+
};
64+
if ret != 0 {
65+
return Err(Error::BindParams);
6866
}
6967
}
7068
Ok(())
@@ -77,7 +75,7 @@ pub trait IntoParams {
7775

7876
impl IntoParams for () {
7977
fn into_params(self) -> Result<Params> {
80-
Ok(Params::None)
78+
Ok(Params::Empty)
8179
}
8280
}
8381

src/row.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,11 @@ impl Row<'_> {
1616
&self.values
1717
}
1818

19-
pub fn get<'i>(&self, index: impl IntoValueIndex<'i>) -> Option<&Value<'_>> {
19+
pub fn get<'i>(&self, index: impl IntoValueIndex<'i>) -> &Value<'_> {
20+
unsafe { self.try_get(index).unwrap_unchecked() }
21+
}
22+
23+
pub fn try_get<'i>(&self, index: impl IntoValueIndex<'i>) -> Option<&Value<'_>> {
2024
match index.into_index() {
2125
ValueIndex::ColumnName(name) => {
2226
let i = self.columns.iter().position(|(n, _)| n == name)?;
@@ -27,7 +31,7 @@ impl Row<'_> {
2731
}
2832

2933
pub fn deserialize<T: DeserializeOwned>(&self) -> Result<T, DeError> {
30-
let deserializer = RowDeserializer { row: &self };
34+
let deserializer = RowDeserializer { row: self };
3135
T::deserialize(deserializer)
3236
}
3337
}

src/statement.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
use params::Params;
2+
3+
use crate::*;
4+
5+
pub struct Statement {
6+
pub(crate) ptr: *mut xdb_stmt_t,
7+
}
8+
9+
impl Drop for Statement {
10+
fn drop(&mut self) {
11+
unsafe {
12+
xdb_stmt_close(self.ptr);
13+
}
14+
}
15+
}
16+
17+
impl Statement {
18+
pub fn query(&self, params: impl IntoParams) -> Result<Query> {
19+
unsafe {
20+
let params = params.into_params()?;
21+
if let Params::Positional(params) = params {
22+
if !params.is_empty() {
23+
self.clear_bindings()?;
24+
Params::bind(self.ptr, params)?;
25+
}
26+
}
27+
let ptr = xdb_stmt_exec(self.ptr);
28+
Query::from_res(ptr)
29+
}
30+
}
31+
32+
pub fn execute(&self, params: impl IntoParams) -> Result<u64> {
33+
self.query(params).map(|q| q.affected_rows())
34+
}
35+
36+
pub fn clear_bindings(&self) -> Result<()> {
37+
let ret = unsafe { xdb_clear_bindings(self.ptr) };
38+
match ret {
39+
0 => Ok(()),
40+
_ => Err(Error::ClearBindings),
41+
}
42+
}
43+
}

src/value.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,21 @@ pub enum Value<'a> {
1212
Char(&'a str),
1313
}
1414

15+
impl Display for Value<'_> {
16+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
17+
match self {
18+
Value::Null => write!(f, "NULL"),
19+
Value::I8(v) => write!(f, "{}", v),
20+
Value::I16(v) => write!(f, "{}", v),
21+
Value::I32(v) => write!(f, "{}", v),
22+
Value::I64(v) => write!(f, "{}", v),
23+
Value::F32(v) => write!(f, "{}", v),
24+
Value::F64(v) => write!(f, "{}", v),
25+
Value::Char(v) => write!(f, "{}", v),
26+
}
27+
}
28+
}
29+
1530
impl<'a> Value<'a> {
1631
// TODO: If you know the detailed format, you can access the pointer directly
1732
// https://crossdb.org/client/api-c/#xdb_column_int

0 commit comments

Comments
 (0)