aboutsummaryrefslogtreecommitdiffstats
path: root/py_bindings/src
diff options
context:
space:
mode:
authorJan Tuomi <jan@jantuomi.fi>2025-02-05 17:50:02 +0200
committerJan Tuomi <jan@jantuomi.fi>2025-02-09 23:52:26 +0200
commitd422ffe3d48d2061b4d3ddeb0c08796e4d71a5fa (patch)
tree0b6820b060bf0cbeb2686a53a23c9ebee5654334 /py_bindings/src
parentd4c953dc4bbd190dae4b5be69941f5c42c59af34 (diff)
Remove Recordable trait, implement Python bindings, fixes
Diffstat (limited to 'py_bindings/src')
-rw-r--r--py_bindings/src/lib.rs630
1 files changed, 443 insertions, 187 deletions
diff --git a/py_bindings/src/lib.rs b/py_bindings/src/lib.rs
index 498aaaf..7749839 100644
--- a/py_bindings/src/lib.rs
+++ b/py_bindings/src/lib.rs
@@ -1,212 +1,468 @@
-// use log_db;
-// use pyo3::exceptions::PyException;
-// use pyo3::prelude::*;
+use std::str::FromStr;
-// type Field = String;
+use log_db::{self, OwnedBounds};
+use pyo3::exceptions::PyException;
+use pyo3::prelude::*;
+use rust_decimal::Decimal;
+use std::ops::Bound as StdBound;
-// #[pyclass]
-// #[derive(Clone)]
-// struct ValueType {
-// record_field: log_db::ValueType,
-// }
+type PyRecord = Vec<Value>;
+type PyField = String;
-// #[pymethods]
-// impl ValueType {
-// #[staticmethod]
-// fn int() -> Self {
-// ValueType {
-// record_field: log_db::ValueType::int(),
-// }
-// }
+#[pyclass]
+#[derive(Clone)]
+struct Type {
+ typ: log_db::Type,
+}
-// #[staticmethod]
-// fn float() -> Self {
-// ValueType {
-// record_field: log_db::ValueType::float(),
-// }
-// }
+#[pymethods]
+impl Type {
+ #[staticmethod]
+ fn int() -> Self {
+ Type {
+ typ: log_db::Type::int(),
+ }
+ }
-// #[staticmethod]
-// fn string() -> Self {
-// ValueType {
-// record_field: log_db::ValueType::string(),
-// }
-// }
+ #[staticmethod]
+ fn decimal() -> Self {
+ Type {
+ typ: log_db::Type::decimal(),
+ }
+ }
-// #[staticmethod]
-// fn bytes() -> Self {
-// ValueType {
-// record_field: log_db::ValueType::bytes(),
-// }
-// }
+ #[staticmethod]
+ fn string() -> Self {
+ Type {
+ typ: log_db::Type::string(),
+ }
+ }
-// fn nullable(&self) -> Self {
-// ValueType {
-// record_field: self.record_field.clone().nullable(),
-// }
-// }
-// }
+ #[staticmethod]
+ fn bytes() -> Self {
+ Type {
+ typ: log_db::Type::bytes(),
+ }
+ }
-// #[pyclass]
-// #[derive(Clone)]
-// struct WriteDurability {
-// write_durability: log_db::WriteDurability,
-// }
+ fn nullable(&self) -> Self {
+ Type {
+ typ: self.typ.clone().nullable(),
+ }
+ }
+}
-// #[pyclass]
-// struct Config {
-// #[pyo3(get, set)]
-// data_dir: Option<String>,
-// #[pyo3(get, set)]
-// segment_size: Option<usize>,
-// #[pyo3(get, set)]
-// fields: Option<Vec<(Field, ValueType)>>,
-// #[pyo3(get, set)]
-// primary_key: Option<Field>,
-// #[pyo3(get, set)]
-// secondary_keys: Option<Vec<Field>>,
-// #[pyo3(get, set)]
-// write_durability: Option<WriteDurability>,
-// }
+pub const WRITE_DURABILITY_FLUSH: u8 = 0;
+pub const WRITE_DURABILITY_FLUSH_SYNC: u8 = 1;
-// #[pymethods]
-// impl Config {
-// pub fn initialize(&self) -> PyResult<DB> {
-// let mut config = log_db::DB::configure();
-// if self.data_dir.is_some() {
-// config.data_dir(&self.data_dir.as_ref().unwrap().to_string());
-// }
-// if self.segment_size.is_some() {
-// config.segment_size(self.segment_size.unwrap());
-// }
-// if self.fields.is_some() {
-// let mut fields = Vec::new();
-// for (field, record_field) in self.fields.as_ref().unwrap() {
-// fields.push((field.to_string(), record_field.record_field.clone()));
-// }
-// config.fields(&fields);
-// }
-// if self.primary_key.is_some() {
-// config.primary_key(self.primary_key.as_ref().unwrap().to_string());
-// }
-// if self.secondary_keys.is_some() {
-// let tmp = self.secondary_keys.as_ref().unwrap();
-// config.secondary_keys(&tmp);
-// }
-// if self.write_durability.is_some() {
-// let tmp = self.write_durability.as_ref().unwrap();
-// config.write_durability(tmp.write_durability.clone());
-// }
+pub const READ_CONSISTENCY_EVENTUAL: u8 = 0;
+pub const READ_CONSISTENCY_STRONG: u8 = 1;
-// let db = config
-// .initialize()
-// .map_err(|e| PyException::new_err(e.to_string()))?;
-// Ok(DB { db })
-// }
-// }
+#[pyclass]
+struct Config {
+ data_dir: Option<PyField>,
+ segment_size: Option<usize>,
+ write_durability: Option<log_db::WriteDurability>,
+ read_consistency: Option<log_db::ReadConsistency>,
+ schema: Option<Vec<(PyField, Type)>>,
+ primary_key: Option<PyField>,
+ secondary_keys: Option<Vec<PyField>>,
+}
-// #[pyclass]
-// #[derive(Clone)]
-// struct Value {
-// record_value: log_db::Value,
-// }
+#[pymethods]
+impl Config {
+ pub fn data_dir<'a>(
+ mut slf: PyRefMut<'a, Self>,
+ data_dir: &str,
+ ) -> PyResult<PyRefMut<'a, Self>> {
+ slf.data_dir = Some(data_dir.into());
+ Ok(slf)
+ }
-// #[pymethods]
-// impl Value {
-// #[staticmethod]
-// fn int(value: i64) -> Self {
-// Value {
-// record_value: log_db::Value::Int(value),
-// }
-// }
+ pub fn segment_size<'a>(
+ mut slf: PyRefMut<'a, Self>,
+ segment_size: usize,
+ ) -> PyResult<PyRefMut<'a, Self>> {
+ slf.segment_size = Some(segment_size);
+ Ok(slf)
+ }
-// #[staticmethod]
-// fn float(value: f64) -> Self {
-// Value {
-// record_value: log_db::Value::Float(value),
-// }
-// }
+ pub fn write_durability<'a>(
+ mut slf: PyRefMut<'a, Self>,
+ write_durability: u8,
+ ) -> PyResult<PyRefMut<'a, Self>> {
+ slf.write_durability = Some(match write_durability {
+ WRITE_DURABILITY_FLUSH => log_db::WriteDurability::Flush,
+ WRITE_DURABILITY_FLUSH_SYNC => log_db::WriteDurability::FlushSync,
+ _ => {
+ return Err(PyException::new_err(format!(
+ "Invalid write_durability value: {}",
+ write_durability,
+ )))
+ }
+ });
+ Ok(slf)
+ }
-// #[staticmethod]
-// fn string(value: &str) -> Self {
-// Value {
-// record_value: log_db::Value::String(value.to_string()),
-// }
-// }
+ pub fn read_consistency<'a>(
+ mut slf: PyRefMut<'a, Self>,
+ read_consistency: u8,
+ ) -> PyResult<PyRefMut<'a, Self>> {
+ slf.read_consistency = Some(match read_consistency {
+ READ_CONSISTENCY_EVENTUAL => log_db::ReadConsistency::Eventual,
+ READ_CONSISTENCY_STRONG => log_db::ReadConsistency::Strong,
+ _ => {
+ return Err(PyException::new_err(format!(
+ "Invalid read_consistency value: {}",
+ read_consistency,
+ )))
+ }
+ });
+ Ok(slf)
+ }
-// #[staticmethod]
-// fn bytes(value: &[u8]) -> Self {
-// Value {
-// record_value: log_db::Value::Bytes(value.to_vec()),
-// }
-// }
+ pub fn schema<'a>(
+ mut slf: PyRefMut<'a, Self>,
+ schema: Vec<(PyField, Type)>,
+ ) -> PyResult<PyRefMut<'a, Self>> {
+ slf.schema = Some(schema);
+ Ok(slf)
+ }
-// #[staticmethod]
-// fn null() -> Self {
-// Value {
-// record_value: log_db::Value::Null,
-// }
-// }
-// }
+ pub fn primary_key<'a>(
+ mut slf: PyRefMut<'a, Self>,
+ primary_key: PyField,
+ ) -> PyResult<PyRefMut<'a, Self>> {
+ slf.primary_key = Some(primary_key);
+ Ok(slf)
+ }
-// #[pyclass]
-// struct Record {
-// values: Vec<Value>,
-// }
+ pub fn secondary_keys<'a>(
+ mut slf: PyRefMut<'a, Self>,
+ secondary_keys: Vec<PyField>,
+ ) -> PyResult<PyRefMut<'a, Self>> {
+ slf.secondary_keys = Some(secondary_keys);
+ Ok(slf)
+ }
-// #[pymethods]
-// impl Record {
-// #[new]
-// #[pyo3(signature = (*py_args))]
-// fn new(py_args: Vec<Value>) -> Self {
-// Record { values: py_args }
-// }
-// }
+ pub fn initialize(&self) -> PyResult<DB> {
+ let mut config = log_db::DB::configure();
+ if self.data_dir.is_some() {
+ config = config.data_dir(&self.data_dir.as_ref().unwrap().to_string());
+ }
+ if self.segment_size.is_some() {
+ config = config.segment_size(self.segment_size.unwrap());
+ }
+ if self.write_durability.is_some() {
+ let tmp = self.write_durability.as_ref().unwrap();
+ config = config.write_durability(tmp.clone());
+ }
+ if self.read_consistency.is_some() {
+ let tmp = self.read_consistency.as_ref().unwrap();
+ config = config.read_consistency(tmp.clone());
+ }
+ if self.schema.is_some() {
+ let schema = self
+ .schema
+ .as_ref()
+ .unwrap()
+ .iter()
+ .map(|(name, typ)| (name.clone(), typ.typ.clone()))
+ .collect();
+ config = config.schema(schema);
+ }
+ if self.primary_key.is_some() {
+ config = config.primary_key(self.primary_key.as_ref().unwrap().to_string());
+ }
+ if self.secondary_keys.is_some() {
+ config = config.secondary_keys(self.secondary_keys.as_ref().unwrap().clone());
+ }
-// #[pyclass]
-// struct DB {
-// db: log_db::DB<Field>,
-// }
+ let db = config
+ .from_record(py_from_record)
+ .into_record(py_into_record)
+ .initialize()
+ .map_err(|e| PyException::new_err(e.to_string()))?;
+ Ok(DB { db })
+ }
+}
-// #[pymethods]
-// impl DB {
-// fn upsert(&mut self, record: &Record) -> PyResult<()> {
-// let values: Vec<log_db::Value> = record
-// .values
-// .iter()
-// .map(|v| v.record_value.clone())
-// .collect();
+fn py_from_record(record: Vec<log_db::Value>) -> Vec<Value> {
+ record
+ .into_iter()
+ .map(|value| Value {
+ record_value: value,
+ })
+ .collect()
+}
-// self.db
-// .upsert(&log_db::Record::from(&values))
-// .map_err(|e| PyException::new_err(e.to_string()))?;
-// Ok(())
-// }
+fn py_into_record(record: Vec<Value>) -> Vec<log_db::Value> {
+ record.into_iter().map(|value| value.record_value).collect()
+}
-// #[staticmethod]
-// pub fn configure() -> Config {
-// Config {
-// data_dir: None,
-// segment_size: None,
-// fields: None,
-// primary_key: None,
-// secondary_keys: None,
-// write_durability: None,
-// }
-// }
-// }
+const VALUE_INT: u8 = 0;
+const VALUE_DECIMAL: u8 = 1;
+const VALUE_STRING: u8 = 2;
+const VALUE_BYTES: u8 = 3;
+const VALUE_NULL: u8 = 4;
-// // #[pyfunction]
-// // fn sum_as_string(a: usize, b: usize) -> PyResult<String> {
-// // Ok((a + b).to_string())
-// // }
+#[pyclass]
+#[derive(Clone, PartialEq, Eq)]
+pub struct Value {
+ record_value: log_db::Value,
+}
-// #[pymodule]
-// fn log_db_py(m: &Bound<'_, PyModule>) -> PyResult<()> {
-// //m.add_function(wrap_pyfunction!(sum_as_string, m)?)?;
-// m.add_class::<DB>()?;
-// m.add_class::<ValueType>()?;
-// m.add_class::<Value>()?;
-// m.add_class::<Record>()?;
-// Ok(())
-// }
+#[pymethods]
+impl Value {
+ fn __repr__(&self) -> String {
+ match &self.record_value {
+ log_db::Value::Int(value) => format!("Value.int({})", value),
+ log_db::Value::Decimal(value) => format!("Value.decimal({})", value),
+ log_db::Value::String(value) => {
+ format!("Value.string(\"{}\")", value.replace("\"", "\\\""))
+ }
+ log_db::Value::Bytes(value) => format!("Value.bytes({:?})", value),
+ log_db::Value::Null => "Value.null()".to_string(),
+ }
+ }
+
+ #[staticmethod]
+ fn int(value: i64) -> Self {
+ Value {
+ record_value: log_db::Value::Int(value),
+ }
+ }
+
+ #[staticmethod]
+ fn decimal(value: String) -> Self {
+ Value {
+ record_value: log_db::Value::Decimal(
+ Decimal::from_str(&value).expect(&format!("Invalid Decimal: {}", value)),
+ ),
+ }
+ }
+
+ #[staticmethod]
+ fn string(value: String) -> Self {
+ Value {
+ record_value: log_db::Value::String(value),
+ }
+ }
+
+ #[staticmethod]
+ fn bytes(value: &[u8]) -> Self {
+ Value {
+ record_value: log_db::Value::Bytes(value.to_vec()),
+ }
+ }
+
+ #[staticmethod]
+ fn null() -> Self {
+ Value {
+ record_value: log_db::Value::Null,
+ }
+ }
+
+ pub fn kind(&self) -> u8 {
+ match &self.record_value {
+ log_db::Value::Int(_) => VALUE_INT,
+ log_db::Value::Decimal(_) => VALUE_DECIMAL,
+ log_db::Value::String(_) => VALUE_STRING,
+ log_db::Value::Bytes(_) => VALUE_BYTES,
+ log_db::Value::Null => VALUE_NULL,
+ }
+ }
+
+ pub fn as_int(&self) -> PyResult<i64> {
+ match &self.record_value {
+ log_db::Value::Int(value) => Ok(*value),
+ _ => Err(PyException::new_err("Value is not an Int")),
+ }
+ }
+
+ pub fn as_decimal(&self) -> PyResult<String> {
+ match &self.record_value {
+ log_db::Value::Decimal(value) => Ok(value.to_string()),
+ _ => Err(PyException::new_err("Value is not a Decimal")),
+ }
+ }
+
+ pub fn as_string(&self) -> PyResult<String> {
+ match &self.record_value {
+ log_db::Value::String(value) => Ok(value.clone()),
+ _ => Err(PyException::new_err("Value is not a String")),
+ }
+ }
+
+ pub fn as_bytes(&self) -> PyResult<Vec<u8>> {
+ match &self.record_value {
+ log_db::Value::Bytes(value) => Ok(value.clone()),
+ _ => Err(PyException::new_err("Value is not Bytes")),
+ }
+ }
+
+ pub fn as_null(&self) -> PyResult<()> {
+ match &self.record_value {
+ log_db::Value::Null => Ok(()),
+ _ => Err(PyException::new_err("Value is not Null")),
+ }
+ }
+}
+
+#[pyclass]
+struct DB {
+ db: log_db::DB<PyRecord, String>,
+}
+
+#[pymethods]
+impl DB {
+ #[staticmethod]
+ pub fn configure() -> Config {
+ Config {
+ data_dir: None,
+ segment_size: None,
+ write_durability: None,
+ read_consistency: None,
+ schema: None,
+ primary_key: None,
+ secondary_keys: None,
+ }
+ }
+
+ pub fn upsert(&mut self, record: PyRecord) -> PyResult<()> {
+ self.db
+ .upsert(record)
+ .map_err(|e| PyException::new_err(e.to_string()))?;
+ Ok(())
+ }
+
+ pub fn get(&mut self, key: Value) -> PyResult<Option<PyRecord>> {
+ self.db
+ .get(&key.record_value)
+ .map_err(|e| PyException::new_err(e.to_string()))
+ }
+
+ // TODO: refactor out &String
+ pub fn find_by(&mut self, field: PyField, key: &Value) -> PyResult<Vec<PyRecord>> {
+ self.db
+ .find_by(&field, &key.record_value)
+ .map_err(|e| PyException::new_err(e.to_string()))
+ }
+
+ // batch_find_by
+ pub fn batch_find_by(
+ &mut self,
+ field: PyField,
+ keys: Vec<Value>,
+ ) -> PyResult<Vec<(usize, PyRecord)>> {
+ let keys: Vec<log_db::Value> = keys.into_iter().map(|key| key.record_value).collect();
+ self.db
+ .batch_find_by(&field, &keys)
+ .map_err(|e| PyException::new_err(e.to_string()))
+ }
+
+ pub fn range_by(
+ &mut self,
+ field: PyField,
+ start: &PyRangeBound,
+ end: &PyRangeBound,
+ ) -> PyResult<Vec<PyRecord>> {
+ let range = OwnedBounds::new(
+ match start {
+ PyRangeBound::Unbounded() => StdBound::Unbounded,
+ PyRangeBound::Included(value) => StdBound::Included(value.record_value.clone()),
+ PyRangeBound::Excluded(value) => StdBound::Excluded(value.record_value.clone()),
+ },
+ match end {
+ PyRangeBound::Unbounded() => StdBound::Unbounded,
+ PyRangeBound::Included(value) => StdBound::Included(value.record_value.clone()),
+ PyRangeBound::Excluded(value) => StdBound::Excluded(value.record_value.clone()),
+ },
+ );
+
+ self.db
+ .range_by(&field, range)
+ .map_err(|e| PyException::new_err(e.to_string()))
+ }
+
+ pub fn delete(&mut self, key: &Value) -> PyResult<Option<PyRecord>> {
+ self.db
+ .delete(&key.record_value)
+ .map_err(|e| PyException::new_err(e.to_string()))
+ }
+
+ pub fn delete_by(&mut self, field: PyField, key: &Value) -> PyResult<Vec<PyRecord>> {
+ self.db
+ .delete_by(&field, &key.record_value)
+ .map_err(|e| PyException::new_err(e.to_string()))
+ }
+
+ pub fn tx_begin(&mut self) -> PyResult<()> {
+ self.db
+ .tx_begin()
+ .map_err(|e| PyException::new_err(e.to_string()))?;
+ Ok(())
+ }
+
+ pub fn tx_commit(&mut self) -> PyResult<()> {
+ self.db
+ .tx_commit()
+ .map_err(|e| PyException::new_err(e.to_string()))?;
+ Ok(())
+ }
+
+ pub fn tx_rollback(&mut self) -> PyResult<()> {
+ self.db
+ .tx_rollback()
+ .map_err(|e| PyException::new_err(e.to_string()))?;
+ Ok(())
+ }
+}
+
+#[pyclass(name = "Bound", eq)]
+#[derive(Clone, PartialEq, Eq)]
+pub enum PyRangeBound {
+ Unbounded(),
+ Included(Value),
+ Excluded(Value),
+}
+
+#[pymethods]
+impl PyRangeBound {
+ #[staticmethod]
+ pub fn unbounded() -> Self {
+ PyRangeBound::Unbounded()
+ }
+
+ #[staticmethod]
+ pub fn included(value: Value) -> Self {
+ PyRangeBound::Included(value)
+ }
+
+ #[staticmethod]
+ pub fn excluded(value: Value) -> Self {
+ PyRangeBound::Excluded(value)
+ }
+}
+
+#[pymodule(name = "log_db")]
+fn log_db_py(m: &Bound<'_, PyModule>) -> PyResult<()> {
+ m.add_class::<DB>()?;
+ m.add_class::<Type>()?;
+ m.add_class::<Value>()?;
+ m.add_class::<PyRangeBound>()?;
+
+ m.add("WRITE_DURABILITY_FLUSH", WRITE_DURABILITY_FLUSH)?;
+ m.add("WRITE_DURABILITY_FLUSH_SYNC", WRITE_DURABILITY_FLUSH_SYNC)?;
+
+ m.add("READ_CONSISTENCY_EVENTUAL", READ_CONSISTENCY_EVENTUAL)?;
+ m.add("READ_CONSISTENCY_STRONG", READ_CONSISTENCY_STRONG)?;
+
+ m.add("VALUE_INT", VALUE_INT)?;
+ m.add("VALUE_DECIMAL", VALUE_DECIMAL)?;
+ m.add("VALUE_STRING", VALUE_STRING)?;
+ m.add("VALUE_BYTES", VALUE_BYTES)?;
+ m.add("VALUE_NULL", VALUE_NULL)?;
+
+ Ok(())
+}