diff --git a/src/driver/inner_connection.rs b/src/driver/inner_connection.rs index 10b861f1..ae060baa 100644 --- a/src/driver/inner_connection.rs +++ b/src/driver/inner_connection.rs @@ -8,7 +8,11 @@ use tokio_postgres::{Client, CopyInSink, Row, Statement, ToStatement}; use crate::{ exceptions::rust_errors::{RustPSQLDriverError, RustPSQLDriverPyResult}, query_result::{PSQLDriverPyQueryResult, PSQLDriverSinglePyQueryResult}, - value_converter::{convert_parameters_and_qs, postgres_to_py, PythonDTO, QueryParameter}, + value_converter::{ + consts::QueryParameter, + funcs::{from_python::convert_parameters_and_qs, to_python::postgres_to_py}, + models::dto::PythonDTO, + }, }; #[allow(clippy::module_name_repetitions)] diff --git a/src/extra_types.rs b/src/extra_types.rs index e0b33be8..1e8d22b4 100644 --- a/src/extra_types.rs +++ b/src/extra_types.rs @@ -10,11 +10,13 @@ use pyo3::{ use serde_json::Value; use crate::{ - additional_types::{Circle as RustCircle, Line as RustLine}, exceptions::rust_errors::{RustPSQLDriverError, RustPSQLDriverPyResult}, value_converter::{ - build_flat_geo_coords, build_geo_coords, build_serde_value, - py_sequence_into_postgres_array, PythonDTO, + additional_types::{Circle as RustCircle, Line as RustLine}, + funcs::from_python::{ + build_flat_geo_coords, build_geo_coords, py_sequence_into_postgres_array, + }, + models::{dto::PythonDTO, serde_value::build_serde_value}, }, }; diff --git a/src/lib.rs b/src/lib.rs index e3602311..e0e1fe11 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,3 @@ -pub mod additional_types; pub mod common; pub mod driver; pub mod exceptions; diff --git a/src/query_result.rs b/src/query_result.rs index 162be3b5..da393f89 100644 --- a/src/query_result.rs +++ b/src/query_result.rs @@ -1,7 +1,10 @@ use pyo3::{prelude::*, pyclass, pymethods, types::PyDict, Py, PyAny, Python, ToPyObject}; use tokio_postgres::Row; -use crate::{exceptions::rust_errors::RustPSQLDriverPyResult, value_converter::postgres_to_py}; +use crate::{ + exceptions::rust_errors::RustPSQLDriverPyResult, + value_converter::funcs::to_python::postgres_to_py, +}; /// Convert postgres `Row` into Python Dict. /// diff --git a/src/value_converter.rs b/src/value_converter.rs deleted file mode 100644 index f3a95297..00000000 --- a/src/value_converter.rs +++ /dev/null @@ -1,2232 +0,0 @@ -use chrono::{self, DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone}; -use chrono_tz::Tz; -use geo_types::{coord, Coord, Line as LineSegment, LineString, Point, Rect}; -use itertools::Itertools; -use macaddr::{MacAddr6, MacAddr8}; -use once_cell::sync::Lazy; -use pg_interval::Interval; -use postgres_types::{Field, FromSql, Kind, ToSql}; -use rust_decimal::Decimal; -use serde_json::{json, Map, Value}; -use std::{collections::HashMap, fmt::Debug, net::IpAddr, sync::RwLock}; -use uuid::Uuid; - -use bytes::{BufMut, BytesMut}; -use postgres_protocol::types; -use pyo3::{ - sync::GILOnceCell, - types::{ - PyAnyMethods, PyBool, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyDictMethods, PyFloat, - PyInt, PyList, PyListMethods, PyMapping, PySequence, PySet, PyString, PyTime, PyTuple, - PyType, PyTypeMethods, - }, - Bound, FromPyObject, IntoPy, Py, PyAny, PyObject, PyResult, Python, ToPyObject, -}; -use tokio_postgres::{ - types::{to_sql_checked, Type}, - Column, Row, -}; - -use crate::{ - additional_types::{ - Circle, Line, RustLineSegment, RustLineString, RustMacAddr6, RustMacAddr8, RustPoint, - RustRect, - }, - exceptions::rust_errors::{RustPSQLDriverError, RustPSQLDriverPyResult}, - extra_types, -}; -use pgvector::Vector as PgVector; -use postgres_array::{array::Array, Dimension}; - -static DECIMAL_CLS: GILOnceCell> = GILOnceCell::new(); -static TIMEDELTA_CLS: GILOnceCell> = GILOnceCell::new(); -static KWARGS_QUERYSTRINGS: Lazy)>>> = - Lazy::new(|| RwLock::new(Default::default())); - -pub type QueryParameter = (dyn ToSql + Sync); - -fn get_decimal_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { - DECIMAL_CLS - .get_or_try_init(py, || { - let type_object = py.import("decimal")?.getattr("Decimal")?.downcast_into()?; - Ok(type_object.unbind()) - }) - .map(|ty| ty.bind(py)) -} - -fn get_timedelta_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { - TIMEDELTA_CLS - .get_or_try_init(py, || { - let type_object = py - .import("datetime")? - .getattr("timedelta")? - .downcast_into()?; - Ok(type_object.unbind()) - }) - .map(|ty| ty.bind(py)) -} - -/// Struct for Uuid. -/// -/// We use custom struct because we need to implement external traits -/// to it. -#[derive(Clone, Copy)] -pub struct InternalUuid(Uuid); - -impl<'a> FromPyObject<'a> for InternalUuid { - fn extract_bound(obj: &Bound<'a, PyAny>) -> PyResult { - let uuid_value = Uuid::parse_str(obj.str()?.extract::<&str>()?).map_err(|_| { - RustPSQLDriverError::PyToRustValueConversionError( - "Cannot convert UUID Array to inner rust type, check you parameters.".into(), - ) - })?; - Ok(InternalUuid(uuid_value)) - } -} - -impl ToPyObject for InternalUuid { - fn to_object(&self, py: Python<'_>) -> PyObject { - self.0.to_string().as_str().to_object(py) - } -} - -impl<'a> FromSql<'a> for InternalUuid { - fn from_sql( - ty: &Type, - raw: &'a [u8], - ) -> Result> { - Ok(InternalUuid(::from_sql(ty, raw)?)) - } - - fn accepts(_ty: &Type) -> bool { - true - } -} - -/// Struct for Value. -/// -/// We use custom struct because we need to implement external traits -/// to it. -#[derive(Clone)] -pub struct InternalSerdeValue(Value); - -impl<'a> FromPyObject<'a> for InternalSerdeValue { - fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult { - let serde_value = build_serde_value(ob.clone().unbind())?; - - Ok(InternalSerdeValue(serde_value)) - } -} - -impl ToPyObject for InternalSerdeValue { - fn to_object(&self, py: Python<'_>) -> PyObject { - match build_python_from_serde_value(py, self.0.clone()) { - Ok(ok_value) => ok_value, - Err(_) => py.None(), - } - } -} - -impl<'a> FromSql<'a> for InternalSerdeValue { - fn from_sql( - ty: &Type, - raw: &'a [u8], - ) -> Result> { - Ok(InternalSerdeValue(::from_sql(ty, raw)?)) - } - - fn accepts(_ty: &Type) -> bool { - true - } -} - -/// Struct for Decimal. -/// -/// It's necessary because we use custom forks and there is -/// no implementation of `ToPyObject` for Decimal. -struct InnerDecimal(Decimal); - -impl ToPyObject for InnerDecimal { - fn to_object(&self, py: Python<'_>) -> PyObject { - let dec_cls = get_decimal_cls(py).expect("failed to load decimal.Decimal"); - let ret = dec_cls - .call1((self.0.to_string(),)) - .expect("failed to call decimal.Decimal(value)"); - ret.to_object(py) - } -} - -impl<'a> FromSql<'a> for InnerDecimal { - fn from_sql( - ty: &Type, - raw: &'a [u8], - ) -> Result> { - Ok(InnerDecimal(::from_sql(ty, raw)?)) - } - - fn accepts(_ty: &Type) -> bool { - true - } -} - -struct InnerInterval(Interval); - -impl ToPyObject for InnerInterval { - fn to_object(&self, py: Python<'_>) -> PyObject { - let td_cls = get_timedelta_cls(py).expect("failed to load datetime.timedelta"); - let pydict = PyDict::new_bound(py); - let months = self.0.months * 30; - let _ = pydict.set_item("days", self.0.days + months); - let _ = pydict.set_item("microseconds", self.0.microseconds); - let ret = td_cls - .call((), Some(&pydict)) - .expect("failed to call datetime.timedelta(days=<>, microseconds=<>)"); - ret.to_object(py) - } -} - -impl<'a> FromSql<'a> for InnerInterval { - fn from_sql( - ty: &Type, - raw: &'a [u8], - ) -> Result> { - Ok(InnerInterval(::from_sql(ty, raw)?)) - } - - fn accepts(_ty: &Type) -> bool { - true - } -} - -/// Additional type for types come from Python. -/// -/// It's necessary because we need to pass this -/// enum into `to_sql` method of `ToSql` trait from -/// `postgres` crate. -#[derive(Debug, Clone, PartialEq)] -pub enum PythonDTO { - // Primitive - PyNone, - PyBytes(Vec), - PyBool(bool), - PyUUID(Uuid), - PyVarChar(String), - PyText(String), - PyString(String), - PyIntI16(i16), - PyIntI32(i32), - PyIntI64(i64), - PyIntU32(u32), - PyIntU64(u64), - PyFloat32(f32), - PyFloat64(f64), - PyMoney(i64), - PyDate(NaiveDate), - PyTime(NaiveTime), - PyDateTime(NaiveDateTime), - PyDateTimeTz(DateTime), - PyInterval(Interval), - PyIpAddress(IpAddr), - PyList(Vec), - PyArray(Array), - PyTuple(Vec), - PyJsonb(Value), - PyJson(Value), - PyMacAddr6(MacAddr6), - PyMacAddr8(MacAddr8), - PyDecimal(Decimal), - PyCustomType(Vec), - PyPoint(Point), - PyBox(Rect), - PyPath(LineString), - PyLine(Line), - PyLineSegment(LineSegment), - PyCircle(Circle), - // Arrays - PyBoolArray(Array), - PyUuidArray(Array), - PyVarCharArray(Array), - PyTextArray(Array), - PyInt16Array(Array), - PyInt32Array(Array), - PyInt64Array(Array), - PyFloat32Array(Array), - PyFloat64Array(Array), - PyMoneyArray(Array), - PyIpAddressArray(Array), - PyJSONBArray(Array), - PyJSONArray(Array), - PyDateArray(Array), - PyTimeArray(Array), - PyDateTimeArray(Array), - PyDateTimeTZArray(Array), - PyMacAddr6Array(Array), - PyMacAddr8Array(Array), - PyNumericArray(Array), - PyPointArray(Array), - PyBoxArray(Array), - PyPathArray(Array), - PyLineArray(Array), - PyLsegArray(Array), - PyCircleArray(Array), - PyIntervalArray(Array), - // PgVector - PyPgVector(Vec), -} - -impl ToPyObject for PythonDTO { - fn to_object(&self, py: Python<'_>) -> PyObject { - match self { - PythonDTO::PyNone => py.None(), - PythonDTO::PyBool(pybool) => pybool.to_object(py), - PythonDTO::PyString(py_string) - | PythonDTO::PyText(py_string) - | PythonDTO::PyVarChar(py_string) => py_string.to_object(py), - PythonDTO::PyIntI32(pyint) => pyint.to_object(py), - PythonDTO::PyIntI64(pyint) => pyint.to_object(py), - PythonDTO::PyIntU64(pyint) => pyint.to_object(py), - PythonDTO::PyFloat32(pyfloat) => pyfloat.to_object(py), - PythonDTO::PyFloat64(pyfloat) => pyfloat.to_object(py), - _ => unreachable!(), - } - } -} - -impl PythonDTO { - /// Return type of the Array for `PostgreSQL`. - /// - /// Since every Array must have concrete type, - /// we must say exactly what type of array we try to pass into - /// postgres. - /// - /// # Errors - /// May return Err Result if there is no support for passed python type. - pub fn array_type(&self) -> RustPSQLDriverPyResult { - match self { - PythonDTO::PyBool(_) => Ok(tokio_postgres::types::Type::BOOL_ARRAY), - PythonDTO::PyUUID(_) => Ok(tokio_postgres::types::Type::UUID_ARRAY), - PythonDTO::PyVarChar(_) | PythonDTO::PyString(_) => { - Ok(tokio_postgres::types::Type::VARCHAR_ARRAY) - } - PythonDTO::PyText(_) => Ok(tokio_postgres::types::Type::TEXT_ARRAY), - PythonDTO::PyIntI16(_) => Ok(tokio_postgres::types::Type::INT2_ARRAY), - PythonDTO::PyIntI32(_) | PythonDTO::PyIntU32(_) => { - Ok(tokio_postgres::types::Type::INT4_ARRAY) - } - PythonDTO::PyIntI64(_) => Ok(tokio_postgres::types::Type::INT8_ARRAY), - PythonDTO::PyFloat32(_) => Ok(tokio_postgres::types::Type::FLOAT4_ARRAY), - PythonDTO::PyFloat64(_) => Ok(tokio_postgres::types::Type::FLOAT8_ARRAY), - PythonDTO::PyMoney(_) => Ok(tokio_postgres::types::Type::MONEY_ARRAY), - PythonDTO::PyIpAddress(_) => Ok(tokio_postgres::types::Type::INET_ARRAY), - PythonDTO::PyJsonb(_) => Ok(tokio_postgres::types::Type::JSONB_ARRAY), - PythonDTO::PyJson(_) => Ok(tokio_postgres::types::Type::JSON_ARRAY), - PythonDTO::PyDate(_) => Ok(tokio_postgres::types::Type::DATE_ARRAY), - PythonDTO::PyTime(_) => Ok(tokio_postgres::types::Type::TIME_ARRAY), - PythonDTO::PyDateTime(_) => Ok(tokio_postgres::types::Type::TIMESTAMP_ARRAY), - PythonDTO::PyDateTimeTz(_) => Ok(tokio_postgres::types::Type::TIMESTAMPTZ_ARRAY), - PythonDTO::PyMacAddr6(_) => Ok(tokio_postgres::types::Type::MACADDR_ARRAY), - PythonDTO::PyMacAddr8(_) => Ok(tokio_postgres::types::Type::MACADDR8_ARRAY), - PythonDTO::PyDecimal(_) => Ok(tokio_postgres::types::Type::NUMERIC_ARRAY), - PythonDTO::PyPoint(_) => Ok(tokio_postgres::types::Type::POINT_ARRAY), - PythonDTO::PyBox(_) => Ok(tokio_postgres::types::Type::BOX_ARRAY), - PythonDTO::PyPath(_) => Ok(tokio_postgres::types::Type::PATH_ARRAY), - PythonDTO::PyLine(_) => Ok(tokio_postgres::types::Type::LINE_ARRAY), - PythonDTO::PyLineSegment(_) => Ok(tokio_postgres::types::Type::LSEG_ARRAY), - PythonDTO::PyCircle(_) => Ok(tokio_postgres::types::Type::CIRCLE_ARRAY), - PythonDTO::PyInterval(_) => Ok(tokio_postgres::types::Type::INTERVAL_ARRAY), - _ => Err(RustPSQLDriverError::PyToRustValueConversionError( - "Can't process array type, your type doesn't have support yet".into(), - )), - } - } - - /// Convert enum into serde `Value`. - /// - /// # Errors - /// May return Err Result if cannot convert python type into rust. - pub fn to_serde_value(&self) -> RustPSQLDriverPyResult { - match self { - PythonDTO::PyNone => Ok(Value::Null), - PythonDTO::PyBool(pybool) => Ok(json!(pybool)), - PythonDTO::PyString(pystring) - | PythonDTO::PyText(pystring) - | PythonDTO::PyVarChar(pystring) => Ok(json!(pystring)), - PythonDTO::PyIntI32(pyint) => Ok(json!(pyint)), - PythonDTO::PyIntI64(pyint) => Ok(json!(pyint)), - PythonDTO::PyIntU64(pyint) => Ok(json!(pyint)), - PythonDTO::PyFloat32(pyfloat) => Ok(json!(pyfloat)), - PythonDTO::PyFloat64(pyfloat) => Ok(json!(pyfloat)), - PythonDTO::PyList(pylist) => { - let mut vec_serde_values: Vec = vec![]; - - for py_object in pylist { - vec_serde_values.push(py_object.to_serde_value()?); - } - - Ok(json!(vec_serde_values)) - } - PythonDTO::PyArray(array) => Ok(json!(pythondto_array_to_serde(Some(array.clone()))?)), - PythonDTO::PyJsonb(py_dict) | PythonDTO::PyJson(py_dict) => Ok(py_dict.clone()), - _ => Err(RustPSQLDriverError::PyToRustValueConversionError( - "Cannot convert your type into Rust type".into(), - )), - } - } -} - -/// Implement `ToSql` trait. -/// -/// It allows us to pass `PythonDTO` enum as parameter -/// directly into `.execute()` method in -/// `DatabasePool`, `Connection` and `Transaction`. -impl ToSql for PythonDTO { - /// Answer the question Is this type can be passed into sql? - /// - /// Always True. - fn accepts(_ty: &tokio_postgres::types::Type) -> bool - where - Self: Sized, - { - true - } - - /// Convert our `PythonDTO` enum into bytes. - /// - /// We convert every inner type of `PythonDTO` enum variant - /// into bytes and write them into bytes buffer. - /// - /// # Errors - /// - /// May return Err Result if cannot write bytes into buffer. - #[allow(clippy::too_many_lines)] - fn to_sql( - &self, - ty: &tokio_postgres::types::Type, - out: &mut BytesMut, - ) -> Result> - where - Self: Sized, - { - let mut return_is_null_true: bool = false; - if *self == PythonDTO::PyNone { - return_is_null_true = true; - } - - match self { - PythonDTO::PyNone => {} - PythonDTO::PyCustomType(some_bytes) => { - <&[u8] as ToSql>::to_sql(&some_bytes.as_slice(), ty, out)?; - } - PythonDTO::PyBytes(pybytes) => { - as ToSql>::to_sql(pybytes, ty, out)?; - } - PythonDTO::PyBool(boolean) => types::bool_to_sql(*boolean, out), - PythonDTO::PyVarChar(string) => { - <&str as ToSql>::to_sql(&string.as_str(), ty, out)?; - } - PythonDTO::PyText(string) => { - <&str as ToSql>::to_sql(&string.as_str(), ty, out)?; - } - PythonDTO::PyUUID(pyuuid) => { - ::to_sql(pyuuid, ty, out)?; - } - PythonDTO::PyString(string) => { - <&str as ToSql>::to_sql(&string.as_str(), ty, out)?; - } - PythonDTO::PyIntI16(int) => out.put_i16(*int), - PythonDTO::PyIntI32(int) => out.put_i32(*int), - PythonDTO::PyIntI64(int) | PythonDTO::PyMoney(int) => out.put_i64(*int), - PythonDTO::PyIntU32(int) => out.put_u32(*int), - PythonDTO::PyIntU64(int) => out.put_u64(*int), - PythonDTO::PyFloat32(float) => out.put_f32(*float), - PythonDTO::PyFloat64(float) => out.put_f64(*float), - PythonDTO::PyDate(pydate) => { - <&NaiveDate as ToSql>::to_sql(&pydate, ty, out)?; - } - PythonDTO::PyTime(pytime) => { - <&NaiveTime as ToSql>::to_sql(&pytime, ty, out)?; - } - PythonDTO::PyDateTime(pydatetime_no_tz) => { - <&NaiveDateTime as ToSql>::to_sql(&pydatetime_no_tz, ty, out)?; - } - PythonDTO::PyDateTimeTz(pydatetime_tz) => { - <&DateTime as ToSql>::to_sql(&pydatetime_tz, ty, out)?; - } - PythonDTO::PyInterval(pyinterval) => { - <&Interval as ToSql>::to_sql(&pyinterval, ty, out)?; - } - PythonDTO::PyIpAddress(pyidaddress) => { - <&IpAddr as ToSql>::to_sql(&pyidaddress, ty, out)?; - } - PythonDTO::PyMacAddr6(pymacaddr) => { - <&[u8] as ToSql>::to_sql(&pymacaddr.as_bytes(), ty, out)?; - } - PythonDTO::PyMacAddr8(pymacaddr) => { - <&[u8] as ToSql>::to_sql(&pymacaddr.as_bytes(), ty, out)?; - } - PythonDTO::PyPoint(pypoint) => { - <&RustPoint as ToSql>::to_sql(&&RustPoint::new(*pypoint), ty, out)?; - } - PythonDTO::PyBox(pybox) => { - <&RustRect as ToSql>::to_sql(&&RustRect::new(*pybox), ty, out)?; - } - PythonDTO::PyPath(pypath) => { - <&RustLineString as ToSql>::to_sql(&&RustLineString::new(pypath.clone()), ty, out)?; - } - PythonDTO::PyLine(pyline) => { - <&Line as ToSql>::to_sql(&pyline, ty, out)?; - } - PythonDTO::PyLineSegment(pylinesegment) => { - <&RustLineSegment as ToSql>::to_sql( - &&RustLineSegment::new(*pylinesegment), - ty, - out, - )?; - } - PythonDTO::PyCircle(pycircle) => { - <&Circle as ToSql>::to_sql(&pycircle, ty, out)?; - } - PythonDTO::PyList(py_iterable) | PythonDTO::PyTuple(py_iterable) => { - let mut items = Vec::new(); - for inner in py_iterable { - items.push(inner); - } - if items.is_empty() { - return_is_null_true = true; - } else { - items.to_sql(&items[0].array_type()?, out)?; - } - } - PythonDTO::PyArray(array) => { - if let Some(first_elem) = array.iter().nth(0) { - match first_elem.array_type() { - Ok(ok_type) => { - array.to_sql(&ok_type, out)?; - } - Err(_) => { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "Cannot define array type.".into(), - ))? - } - } - } - } - PythonDTO::PyJsonb(py_dict) | PythonDTO::PyJson(py_dict) => { - <&Value as ToSql>::to_sql(&py_dict, ty, out)?; - } - PythonDTO::PyDecimal(py_decimal) => { - ::to_sql(py_decimal, ty, out)?; - } - PythonDTO::PyBoolArray(array) => { - array.to_sql(&Type::BOOL_ARRAY, out)?; - } - PythonDTO::PyUuidArray(array) => { - array.to_sql(&Type::UUID_ARRAY, out)?; - } - PythonDTO::PyVarCharArray(array) => { - array.to_sql(&Type::VARCHAR_ARRAY, out)?; - } - PythonDTO::PyTextArray(array) => { - array.to_sql(&Type::TEXT_ARRAY, out)?; - } - PythonDTO::PyInt16Array(array) => { - array.to_sql(&Type::INT2_ARRAY, out)?; - } - PythonDTO::PyInt32Array(array) => { - array.to_sql(&Type::INT4_ARRAY, out)?; - } - PythonDTO::PyInt64Array(array) => { - array.to_sql(&Type::INT8_ARRAY, out)?; - } - PythonDTO::PyFloat32Array(array) => { - array.to_sql(&Type::FLOAT4, out)?; - } - PythonDTO::PyFloat64Array(array) => { - array.to_sql(&Type::FLOAT8_ARRAY, out)?; - } - PythonDTO::PyMoneyArray(array) => { - array.to_sql(&Type::MONEY_ARRAY, out)?; - } - PythonDTO::PyIpAddressArray(array) => { - array.to_sql(&Type::INET_ARRAY, out)?; - } - PythonDTO::PyJSONBArray(array) => { - array.to_sql(&Type::JSONB_ARRAY, out)?; - } - PythonDTO::PyJSONArray(array) => { - array.to_sql(&Type::JSON_ARRAY, out)?; - } - PythonDTO::PyDateArray(array) => { - array.to_sql(&Type::DATE_ARRAY, out)?; - } - PythonDTO::PyTimeArray(array) => { - array.to_sql(&Type::TIME_ARRAY, out)?; - } - PythonDTO::PyDateTimeArray(array) => { - array.to_sql(&Type::TIMESTAMP_ARRAY, out)?; - } - PythonDTO::PyDateTimeTZArray(array) => { - array.to_sql(&Type::TIMESTAMPTZ_ARRAY, out)?; - } - PythonDTO::PyMacAddr6Array(array) => { - array.to_sql(&Type::MACADDR_ARRAY, out)?; - } - PythonDTO::PyMacAddr8Array(array) => { - array.to_sql(&Type::MACADDR8_ARRAY, out)?; - } - PythonDTO::PyNumericArray(array) => { - array.to_sql(&Type::NUMERIC_ARRAY, out)?; - } - PythonDTO::PyPointArray(array) => { - array.to_sql(&Type::POINT_ARRAY, out)?; - } - PythonDTO::PyBoxArray(array) => { - array.to_sql(&Type::BOX_ARRAY, out)?; - } - PythonDTO::PyPathArray(array) => { - array.to_sql(&Type::PATH_ARRAY, out)?; - } - PythonDTO::PyLineArray(array) => { - array.to_sql(&Type::LINE_ARRAY, out)?; - } - PythonDTO::PyLsegArray(array) => { - array.to_sql(&Type::LSEG_ARRAY, out)?; - } - PythonDTO::PyCircleArray(array) => { - array.to_sql(&Type::CIRCLE_ARRAY, out)?; - } - PythonDTO::PyIntervalArray(array) => { - array.to_sql(&Type::INTERVAL_ARRAY, out)?; - } - PythonDTO::PyPgVector(vector) => { - ::to_sql(&PgVector::from(vector.clone()), ty, out)?; - } - } - - if return_is_null_true { - Ok(tokio_postgres::types::IsNull::Yes) - } else { - Ok(tokio_postgres::types::IsNull::No) - } - } - - to_sql_checked!(); -} - -fn parse_kwargs_qs(querystring: &str) -> (String, Vec) { - let re = regex::Regex::new(r"\$\(([^)]+)\)p").unwrap(); - - { - let kq_read = KWARGS_QUERYSTRINGS.read().unwrap(); - let qs = kq_read.get(querystring); - - if let Some(qs) = qs { - return qs.clone(); - } - }; - - let mut counter = 0; - let mut sequence = Vec::new(); - - let result = re.replace_all(querystring, |caps: ®ex::Captures| { - let account_id = caps[1].to_string(); - - sequence.push(account_id.clone()); - counter += 1; - - format!("${}", &counter) - }); - - let mut kq_write = KWARGS_QUERYSTRINGS.write().unwrap(); - kq_write.insert( - querystring.to_string(), - (result.clone().into(), sequence.clone()), - ); - (result.into(), sequence) -} - -pub fn convert_kwargs_parameters<'a>( - kw_params: &Bound<'_, PyMapping>, - querystring: &'a str, -) -> RustPSQLDriverPyResult<(String, Vec)> { - let mut result_vec: Vec = vec![]; - let (changed_string, params_names) = parse_kwargs_qs(querystring); - - for param_name in params_names { - match kw_params.get_item(¶m_name) { - Ok(param) => result_vec.push(py_to_rust(¶m)?), - Err(_) => { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - format!("Cannot find parameter with name <{param_name}> in parameters").into(), - )) - } - } - } - - Ok((changed_string, result_vec)) -} - -pub fn convert_seq_parameters( - seq_params: Vec>, -) -> RustPSQLDriverPyResult> { - let mut result_vec: Vec = vec![]; - Python::with_gil(|gil| { - for parameter in seq_params { - result_vec.push(py_to_rust(parameter.bind(gil))?); - } - Ok::<(), RustPSQLDriverError>(()) - })?; - - Ok(result_vec) -} - -/// Convert parameters come from python. -/// -/// Parameters for `execute()` method can be either -/// a list or a tuple or a set. -/// -/// We parse every parameter from python object and return -/// Vector of out `PythonDTO`. -/// -/// # Errors -/// -/// May return Err Result if can't convert python object. -#[allow(clippy::needless_pass_by_value)] -pub fn convert_parameters_and_qs( - querystring: String, - parameters: Option>, -) -> RustPSQLDriverPyResult<(String, Vec)> { - let Some(parameters) = parameters else { - return Ok((querystring, vec![])); - }; - - let res = Python::with_gil(|gil| { - let params = parameters.extract::>>(gil).map_err(|_| { - RustPSQLDriverError::PyToRustValueConversionError( - "Cannot convert you parameters argument into Rust type, please use List/Tuple" - .into(), - ) - }); - if let Ok(params) = params { - return Ok((querystring, convert_seq_parameters(params)?)); - } - - let kw_params = parameters.downcast_bound::(gil); - if let Ok(kw_params) = kw_params { - return convert_kwargs_parameters(kw_params, &querystring); - } - - Err(RustPSQLDriverError::PyToRustValueConversionError( - "Parameters must be sequence or mapping".into(), - )) - })?; - - Ok(res) -} - -/// Convert Sequence from Python (except String) into flat vec. -/// -/// # Errors -/// May return Err Result if cannot convert element into Rust one. -pub fn py_sequence_into_flat_vec( - parameter: &Bound, -) -> RustPSQLDriverPyResult> { - let py_seq = parameter.downcast::().map_err(|_| { - RustPSQLDriverError::PyToRustValueConversionError( - "PostgreSQL ARRAY type can be made only from python Sequence".into(), - ) - })?; - - let mut final_vec: Vec = vec![]; - - for seq_elem in py_seq.iter()? { - let ok_seq_elem = seq_elem?; - - // Check for the string because it's sequence too, - // and in the most cases it should be array type, not new dimension. - if ok_seq_elem.is_instance_of::() { - final_vec.push(py_to_rust(&ok_seq_elem)?); - continue; - } - - let possible_next_seq = ok_seq_elem.downcast::(); - - if let Ok(next_seq) = possible_next_seq { - let mut next_vec = py_sequence_into_flat_vec(next_seq)?; - final_vec.append(&mut next_vec); - } else { - final_vec.push(py_to_rust(&ok_seq_elem)?); - continue; - } - } - - Ok(final_vec) -} - -/// Convert Sequence from Python into Postgres ARRAY. -/// -/// # Errors -/// -/// May return Err Result if cannot convert at least one element. -#[allow(clippy::cast_possible_truncation)] -#[allow(clippy::cast_possible_wrap)] -pub fn py_sequence_into_postgres_array( - parameter: &Bound, -) -> RustPSQLDriverPyResult> { - let mut py_seq = parameter - .downcast::() - .map_err(|_| { - RustPSQLDriverError::PyToRustValueConversionError( - "PostgreSQL ARRAY type can be made only from python Sequence".into(), - ) - })? - .clone(); - - let mut dimensions: Vec = vec![]; - let mut continue_iteration = true; - - while continue_iteration { - dimensions.push(Dimension { - len: py_seq.len()? as i32, - lower_bound: 1, - }); - - let first_seq_elem = py_seq.iter()?.next(); - match first_seq_elem { - Some(first_seq_elem) => { - if let Ok(first_seq_elem) = first_seq_elem { - // Check for the string because it's sequence too, - // and in the most cases it should be array type, not new dimension. - if first_seq_elem.is_instance_of::() { - continue_iteration = false; - continue; - } - let possible_inner_seq = first_seq_elem.downcast::(); - - match possible_inner_seq { - Ok(possible_inner_seq) => { - py_seq = possible_inner_seq.clone(); - } - Err(_) => continue_iteration = false, - } - } - } - None => { - continue_iteration = false; - } - } - } - - let array_data = py_sequence_into_flat_vec(parameter)?; - match postgres_array::Array::from_parts_no_panic(array_data, dimensions) { - Ok(result_array) => Ok(result_array), - Err(err) => Err(RustPSQLDriverError::PyToRustValueConversionError(format!( - "Cannot convert python sequence to PostgreSQL ARRAY, error - {err}" - ))), - } -} - -/// Extract a value from a Python object, raising an error if missing or invalid -/// -/// # Errors -/// This function will return `Err` in the following cases: -/// - The Python object does not have the specified attribute -/// - The attribute exists but cannot be extracted into the specified Rust type -fn extract_value_from_python_object_or_raise<'py, T>( - parameter: &'py pyo3::Bound<'_, PyAny>, - attr_name: &str, -) -> Result -where - T: FromPyObject<'py>, -{ - parameter - .getattr(attr_name) - .ok() - .and_then(|attr| attr.extract::().ok()) - .ok_or_else(|| { - RustPSQLDriverError::PyToRustValueConversionError("Invalid attribute".into()) - }) -} - -/// Extract a timezone-aware datetime from a Python object. -/// This function retrieves various datetime components (`year`, `month`, `day`, etc.) -/// from a Python object and constructs a `DateTime` -/// -/// # Errors -/// This function will return `Err` in the following cases: -/// - The Python object does not contain or support one or more required datetime attributes -/// - The retrieved values are invalid for constructing a date, time, or datetime (e.g., invalid month or day) -/// - The timezone information (`tzinfo`) is not available or cannot be parsed -/// - The resulting datetime is ambiguous or invalid (e.g., due to DST transitions) -fn extract_datetime_from_python_object_attrs( - parameter: &pyo3::Bound<'_, PyAny>, -) -> Result, RustPSQLDriverError> { - let year = extract_value_from_python_object_or_raise::(parameter, "year")?; - let month = extract_value_from_python_object_or_raise::(parameter, "month")?; - let day = extract_value_from_python_object_or_raise::(parameter, "day")?; - let hour = extract_value_from_python_object_or_raise::(parameter, "hour")?; - let minute = extract_value_from_python_object_or_raise::(parameter, "minute")?; - let second = extract_value_from_python_object_or_raise::(parameter, "second")?; - let microsecond = extract_value_from_python_object_or_raise::(parameter, "microsecond")?; - - let date = NaiveDate::from_ymd_opt(year, month, day) - .ok_or_else(|| RustPSQLDriverError::PyToRustValueConversionError("Invalid date".into()))?; - let time = NaiveTime::from_hms_micro_opt(hour, minute, second, microsecond) - .ok_or_else(|| RustPSQLDriverError::PyToRustValueConversionError("Invalid time".into()))?; - let naive_datetime = NaiveDateTime::new(date, time); - - let raw_timestamp_tz = parameter - .getattr("tzinfo") - .ok() - .and_then(|tzinfo| tzinfo.getattr("key").ok()) - .and_then(|key| key.extract::().ok()) - .ok_or_else(|| { - RustPSQLDriverError::PyToRustValueConversionError("Invalid timezone info".into()) - })?; - - let fixed_offset_datetime = raw_timestamp_tz - .parse::() - .map_err(|_| { - RustPSQLDriverError::PyToRustValueConversionError("Failed to parse TZ".into()) - })? - .from_local_datetime(&naive_datetime) - .single() - .ok_or_else(|| { - RustPSQLDriverError::PyToRustValueConversionError( - "Ambiguous or invalid datetime".into(), - ) - })? - .fixed_offset(); - - Ok(fixed_offset_datetime) -} - -/// Convert single python parameter to `PythonDTO` enum. -/// -/// # Errors -/// -/// May return Err Result if python type doesn't have support yet -/// or value of the type is incorrect. -#[allow(clippy::too_many_lines)] -pub fn py_to_rust(parameter: &pyo3::Bound<'_, PyAny>) -> RustPSQLDriverPyResult { - if parameter.is_none() { - return Ok(PythonDTO::PyNone); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyCustomType( - parameter.extract::()?.inner(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyBool(parameter.extract::()?)); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyBytes(parameter.extract::>()?)); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyText( - parameter.extract::()?.inner(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyVarChar( - parameter.extract::()?.inner(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyString(parameter.extract::()?)); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyFloat64(parameter.extract::()?)); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyFloat32( - parameter - .extract::()? - .retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyFloat64( - parameter - .extract::()? - .retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyIntI16( - parameter - .extract::()? - .retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyIntI32( - parameter - .extract::()? - .retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyIntI64( - parameter.extract::()?.retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyMoney( - parameter.extract::()?.retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyIntI32(parameter.extract::()?)); - } - - if parameter.is_instance_of::() { - let timestamp_tz = parameter.extract::>(); - if let Ok(pydatetime_tz) = timestamp_tz { - return Ok(PythonDTO::PyDateTimeTz(pydatetime_tz)); - } - - let timestamp_no_tz = parameter.extract::(); - if let Ok(pydatetime_no_tz) = timestamp_no_tz { - return Ok(PythonDTO::PyDateTime(pydatetime_no_tz)); - } - - let timestamp_tz = extract_datetime_from_python_object_attrs(parameter); - if let Ok(pydatetime_tz) = timestamp_tz { - return Ok(PythonDTO::PyDateTimeTz(pydatetime_tz)); - } - - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "Can not convert you datetime to rust type".into(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyDate(parameter.extract::()?)); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyTime(parameter.extract::()?)); - } - - if parameter.is_instance_of::() { - let duration = parameter.extract::()?; - if let Some(interval) = Interval::from_duration(duration) { - return Ok(PythonDTO::PyInterval(interval)); - } - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "Cannot convert timedelta from Python to inner Rust type.".to_string(), - )); - } - - if parameter.is_instance_of::() | parameter.is_instance_of::() { - return Ok(PythonDTO::PyArray(py_sequence_into_postgres_array( - parameter, - )?)); - } - - if parameter.is_instance_of::() { - let dict = parameter.downcast::().map_err(|error| { - RustPSQLDriverError::PyToRustValueConversionError(format!( - "Can't cast to inner dict: {error}" - )) - })?; - - let mut serde_map: Map = Map::new(); - - for dict_item in dict.items() { - let py_list = dict_item.downcast::().map_err(|error| { - RustPSQLDriverError::PyToRustValueConversionError(format!( - "Cannot cast to list: {error}" - )) - })?; - - let key = py_list.get_item(0)?.extract::()?; - let value = py_to_rust(&py_list.get_item(1)?)?; - - serde_map.insert(key, value.to_serde_value()?); - } - - return Ok(PythonDTO::PyJsonb(Value::Object(serde_map))); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyJsonb( - parameter.extract::()?.inner().clone(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyJson( - parameter.extract::()?.inner().clone(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyMacAddr6( - parameter.extract::()?.inner(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyMacAddr8( - parameter.extract::()?.inner(), - )); - } - - if parameter.get_type().name()? == "UUID" { - return Ok(PythonDTO::PyUUID(Uuid::parse_str( - parameter.str()?.extract::<&str>()?, - )?)); - } - - if parameter.get_type().name()? == "decimal.Decimal" - || parameter.get_type().name()? == "Decimal" - { - return Ok(PythonDTO::PyDecimal(Decimal::from_str_exact( - parameter.str()?.extract::<&str>()?, - )?)); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyPoint( - parameter.extract::()?.retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyBox( - parameter.extract::()?.retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyPath( - parameter.extract::()?.retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyLine( - parameter.extract::()?.retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyLineSegment( - parameter - .extract::()? - .retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyCircle( - parameter.extract::()?.retrieve_value(), - )); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return parameter - .extract::()? - ._convert_to_python_dto(); - } - - if parameter.is_instance_of::() { - return Ok(PythonDTO::PyPgVector( - parameter.extract::()?.inner_value(), - )); - } - - if let Ok(id_address) = parameter.extract::() { - return Ok(PythonDTO::PyIpAddress(id_address)); - } - - // It's used for Enum. - // If StrEnum is used on Python side, - // we simply stop at the `is_instance_of::``. - if let Ok(value_attr) = parameter.getattr("value") { - if let Ok(possible_string) = value_attr.extract::() { - return Ok(PythonDTO::PyString(possible_string)); - } - } - - Err(RustPSQLDriverError::PyToRustValueConversionError(format!( - "Can not covert you type {parameter} into inner one", - ))) -} - -fn composite_field_postgres_to_py<'a, T: FromSql<'a>>( - type_: &Type, - buf: &mut &'a [u8], - is_simple: bool, -) -> RustPSQLDriverPyResult { - if is_simple { - return T::from_sql_nullable(type_, Some(buf)).map_err(|err| { - RustPSQLDriverError::RustToPyValueConversionError(format!( - "Cannot convert PostgreSQL type {type_} into Python type, err: {err}", - )) - }); - } - postgres_types::private::read_value::(type_, buf).map_err(|err| { - RustPSQLDriverError::RustToPyValueConversionError(format!( - "Cannot convert PostgreSQL type {type_} into Python type, err: {err}", - )) - }) -} - -/// Convert Array of `PythonDTO`s to serde `Value`. -/// -/// It can convert multidimensional arrays. -fn pythondto_array_to_serde(array: Option>) -> RustPSQLDriverPyResult { - match array { - Some(array) => inner_pythondto_array_to_serde( - array.dimensions(), - array.iter().collect::>().as_slice(), - 0, - 0, - ), - None => Ok(Value::Null), - } -} - -/// Inner conversion array of `PythonDTO`s to serde `Value`. -#[allow(clippy::cast_sign_loss)] -fn inner_pythondto_array_to_serde( - dimensions: &[Dimension], - data: &[&PythonDTO], - dimension_index: usize, - mut lower_bound: usize, -) -> RustPSQLDriverPyResult { - let current_dimension = dimensions.get(dimension_index); - - if let Some(current_dimension) = current_dimension { - let possible_next_dimension = dimensions.get(dimension_index + 1); - match possible_next_dimension { - Some(next_dimension) => { - let mut final_list: Value = Value::Array(vec![]); - - for _ in 0..current_dimension.len as usize { - if dimensions.get(dimension_index + 1).is_some() { - let inner_pylist = inner_pythondto_array_to_serde( - dimensions, - &data[lower_bound..next_dimension.len as usize + lower_bound], - dimension_index + 1, - 0, - )?; - match final_list { - Value::Array(ref mut array) => array.push(inner_pylist), - _ => unreachable!(), - } - lower_bound += next_dimension.len as usize; - }; - } - - return Ok(final_list); - } - None => { - return data.iter().map(|x| x.to_serde_value()).collect(); - } - } - } - - Ok(Value::Array(vec![])) -} - -/// Convert rust array to python list. -/// -/// It can convert multidimensional arrays. -fn postgres_array_to_py( - py: Python<'_>, - array: Option>, -) -> Option> { - array.map(|array| { - inner_postgres_array_to_py( - py, - array.dimensions(), - array.iter().collect::>().as_slice(), - 0, - 0, - ) - }) -} - -/// Inner postgres array conversion to python list. -#[allow(clippy::cast_sign_loss)] -fn inner_postgres_array_to_py( - py: Python<'_>, - dimensions: &[Dimension], - data: &[T], - dimension_index: usize, - mut lower_bound: usize, -) -> Py -where - T: ToPyObject, -{ - let current_dimension = dimensions.get(dimension_index); - - if let Some(current_dimension) = current_dimension { - let possible_next_dimension = dimensions.get(dimension_index + 1); - match possible_next_dimension { - Some(next_dimension) => { - let final_list = PyList::empty_bound(py); - - for _ in 0..current_dimension.len as usize { - if dimensions.get(dimension_index + 1).is_some() { - let inner_pylist = inner_postgres_array_to_py( - py, - dimensions, - &data[lower_bound..next_dimension.len as usize + lower_bound], - dimension_index + 1, - 0, - ); - final_list.append(inner_pylist).unwrap(); - lower_bound += next_dimension.len as usize; - }; - } - - return final_list.unbind(); - } - None => { - return PyList::new_bound(py, data).unbind(); - } - } - } - - PyList::empty_bound(py).unbind() -} - -#[allow(clippy::too_many_lines)] -fn postgres_bytes_to_py( - py: Python<'_>, - type_: &Type, - buf: &mut &[u8], - is_simple: bool, -) -> RustPSQLDriverPyResult> { - match *type_ { - // ---------- Bytes Types ---------- - // Convert BYTEA type into Vector, then into PyBytes - Type::BYTEA => { - let vec_of_bytes = - composite_field_postgres_to_py::>>(type_, buf, is_simple)?; - if let Some(vec_of_bytes) = vec_of_bytes { - return Ok(PyBytes::new_bound(py, &vec_of_bytes).to_object(py)); - } - Ok(py.None()) - } - // // ---------- String Types ---------- - // // Convert TEXT and VARCHAR type into String, then into str - Type::TEXT | Type::VARCHAR | Type::XML => Ok(composite_field_postgres_to_py::< - Option, - >(type_, buf, is_simple)? - .to_object(py)), - // ---------- Boolean Types ---------- - // Convert BOOL type into bool - Type::BOOL => Ok( - composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py), - ), - // ---------- Number Types ---------- - // Convert SmallInt into i16, then into int - Type::INT2 => { - Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) - } - // Convert Integer into i32, then into int - Type::INT4 => { - Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) - } - // Convert BigInt into i64, then into int - Type::INT8 | Type::MONEY => { - Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) - } - // Convert REAL into f32, then into float - Type::FLOAT4 => { - Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) - } - // Convert DOUBLE PRECISION into f64, then into float - Type::FLOAT8 => { - Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) - } - // ---------- Date Types ---------- - // Convert DATE into NaiveDate, then into datetime.date - Type::DATE => Ok(composite_field_postgres_to_py::>( - type_, buf, is_simple, - )? - .to_object(py)), - // Convert Time into NaiveTime, then into datetime.time - Type::TIME => Ok(composite_field_postgres_to_py::>( - type_, buf, is_simple, - )? - .to_object(py)), - // Convert TIMESTAMP into NaiveDateTime, then into datetime.datetime - Type::TIMESTAMP => Ok(composite_field_postgres_to_py::>( - type_, buf, is_simple, - )? - .to_object(py)), - // Convert TIMESTAMP into NaiveDateTime, then into datetime.datetime - Type::TIMESTAMPTZ => Ok( - composite_field_postgres_to_py::>>(type_, buf, is_simple)? - .to_object(py), - ), - // ---------- UUID Types ---------- - // Convert UUID into Uuid type, then into String if possible - Type::UUID => { - let rust_uuid = composite_field_postgres_to_py::>(type_, buf, is_simple)?; - match rust_uuid { - Some(rust_uuid) => { - return Ok(PyString::new_bound(py, &rust_uuid.to_string()).to_object(py)) - } - None => Ok(py.None()), - } - } - // ---------- IpAddress Types ---------- - Type::INET => Ok( - composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py), - ), - // Convert JSON/JSONB into Serde Value, then into list or dict - Type::JSONB | Type::JSON => { - let db_json = composite_field_postgres_to_py::>(type_, buf, is_simple)?; - - match db_json { - Some(value) => Ok(build_python_from_serde_value(py, value)?), - None => Ok(py.None().to_object(py)), - } - } - // Convert MACADDR into inner type for macaddr6, then into str - Type::MACADDR => { - let macaddr_ = - composite_field_postgres_to_py::>(type_, buf, is_simple)?; - if let Some(macaddr_) = macaddr_ { - Ok(macaddr_.inner().to_string().to_object(py)) - } else { - Ok(py.None().to_object(py)) - } - } - Type::MACADDR8 => { - let macaddr_ = - composite_field_postgres_to_py::>(type_, buf, is_simple)?; - if let Some(macaddr_) = macaddr_ { - Ok(macaddr_.inner().to_string().to_object(py)) - } else { - Ok(py.None().to_object(py)) - } - } - Type::NUMERIC => { - if let Some(numeric_) = - composite_field_postgres_to_py::>(type_, buf, is_simple)? - { - return Ok(InnerDecimal(numeric_).to_object(py)); - } - Ok(py.None().to_object(py)) - } - // ---------- Geo Types ---------- - Type::POINT => { - let point_ = - composite_field_postgres_to_py::>(type_, buf, is_simple)?; - - match point_ { - Some(point_) => Ok(point_.into_py(py)), - None => Ok(py.None().to_object(py)), - } - } - Type::BOX => { - let box_ = composite_field_postgres_to_py::>(type_, buf, is_simple)?; - - match box_ { - Some(box_) => Ok(box_.into_py(py)), - None => Ok(py.None().to_object(py)), - } - } - Type::PATH => { - let path_ = - composite_field_postgres_to_py::>(type_, buf, is_simple)?; - - match path_ { - Some(path_) => Ok(path_.into_py(py)), - None => Ok(py.None().to_object(py)), - } - } - Type::LINE => { - let line_ = composite_field_postgres_to_py::>(type_, buf, is_simple)?; - - match line_ { - Some(line_) => Ok(line_.into_py(py)), - None => Ok(py.None().to_object(py)), - } - } - Type::LSEG => { - let lseg_ = - composite_field_postgres_to_py::>(type_, buf, is_simple)?; - - match lseg_ { - Some(lseg_) => Ok(lseg_.into_py(py)), - None => Ok(py.None().to_object(py)), - } - } - Type::CIRCLE => { - let circle_ = composite_field_postgres_to_py::>(type_, buf, is_simple)?; - - match circle_ { - Some(circle_) => Ok(circle_.into_py(py)), - None => Ok(py.None().to_object(py)), - } - } - Type::INTERVAL => { - let interval = - composite_field_postgres_to_py::>(type_, buf, is_simple)?; - if let Some(interval) = interval { - return Ok(InnerInterval(interval).to_object(py)); - } - Ok(py.None()) - } - // ---------- Array Text Types ---------- - Type::BOOL_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // Convert ARRAY of TEXT or VARCHAR into Vec, then into list[str] - Type::TEXT_ARRAY | Type::VARCHAR_ARRAY | Type::XML_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // ---------- Array Integer Types ---------- - // Convert ARRAY of SmallInt into Vec, then into list[int] - Type::INT2_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // Convert ARRAY of Integer into Vec, then into list[int] - Type::INT4_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // Convert ARRAY of BigInt into Vec, then into list[int] - Type::INT8_ARRAY | Type::MONEY_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // Convert ARRAY of Float4 into Vec, then into list[float] - Type::FLOAT4_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // Convert ARRAY of Float8 into Vec, then into list[float] - Type::FLOAT8_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // Convert ARRAY of Date into Vec, then into list[datetime.date] - Type::DATE_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // Convert ARRAY of Time into Vec, then into list[datetime.date] - Type::TIME_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // Convert ARRAY of TIMESTAMP into Vec, then into list[datetime.date] - Type::TIMESTAMP_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // Convert ARRAY of TIMESTAMPTZ into Vec>, then into list[datetime.date] - Type::TIMESTAMPTZ_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>>( - type_, buf, is_simple, - )?, - ) - .to_object(py)), - // Convert ARRAY of UUID into Vec>, then into list[UUID] - Type::UUID_ARRAY => { - let uuid_array = composite_field_postgres_to_py::>>( - type_, buf, is_simple, - )?; - Ok(postgres_array_to_py(py, uuid_array).to_object(py)) - } - // Convert ARRAY of INET into Vec, then into list[IPv4Address | IPv6Address] - Type::INET_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - Type::JSONB_ARRAY | Type::JSON_ARRAY => { - let db_json_array = composite_field_postgres_to_py::>>( - type_, buf, is_simple, - )?; - Ok(postgres_array_to_py(py, db_json_array).to_object(py)) - } - Type::NUMERIC_ARRAY => Ok(postgres_array_to_py( - py, - composite_field_postgres_to_py::>>(type_, buf, is_simple)?, - ) - .to_object(py)), - // ---------- Array Geo Types ---------- - Type::POINT_ARRAY => { - let point_array_ = - composite_field_postgres_to_py::>>(type_, buf, is_simple)?; - - Ok(postgres_array_to_py(py, point_array_).to_object(py)) - } - Type::BOX_ARRAY => { - let box_array_ = - composite_field_postgres_to_py::>>(type_, buf, is_simple)?; - - Ok(postgres_array_to_py(py, box_array_).to_object(py)) - } - Type::PATH_ARRAY => { - let path_array_ = composite_field_postgres_to_py::>>( - type_, buf, is_simple, - )?; - - Ok(postgres_array_to_py(py, path_array_).to_object(py)) - } - Type::LINE_ARRAY => { - let line_array_ = - composite_field_postgres_to_py::>>(type_, buf, is_simple)?; - - Ok(postgres_array_to_py(py, line_array_).to_object(py)) - } - Type::LSEG_ARRAY => { - let lseg_array_ = composite_field_postgres_to_py::>>( - type_, buf, is_simple, - )?; - - Ok(postgres_array_to_py(py, lseg_array_).to_object(py)) - } - Type::CIRCLE_ARRAY => { - let circle_array_ = - composite_field_postgres_to_py::>>(type_, buf, is_simple)?; - - Ok(postgres_array_to_py(py, circle_array_).to_object(py)) - } - Type::INTERVAL_ARRAY => { - let interval_array_ = composite_field_postgres_to_py::>>( - type_, buf, is_simple, - )?; - - Ok(postgres_array_to_py(py, interval_array_).to_object(py)) - } - _ => other_postgres_bytes_to_py(py, type_, buf, is_simple), - } -} - -/// Convert OTHER type to python. -/// -/// # Errors -/// May return result if type is unknown. -pub fn other_postgres_bytes_to_py( - py: Python<'_>, - type_: &Type, - buf: &mut &[u8], - is_simple: bool, -) -> RustPSQLDriverPyResult> { - if type_.name() == "vector" { - let vector = composite_field_postgres_to_py::>(type_, buf, is_simple)?; - match vector { - Some(real_vector) => { - return Ok(real_vector.to_vec().to_object(py)); - } - None => return Ok(py.None()), - } - } - - Err(RustPSQLDriverError::RustToPyValueConversionError( - format!("Cannot convert {type_} into Python type, please look at the custom_decoders functionality.") - )) -} - -/// Convert composite type from `PostgreSQL` to Python type. -/// -/// # Errors -/// May return error if there is any problem with bytes. -#[allow(clippy::cast_sign_loss)] -pub fn composite_postgres_to_py( - py: Python<'_>, - fields: &Vec, - buf: &mut &[u8], - custom_decoders: &Option>, -) -> RustPSQLDriverPyResult> { - let result_py_dict: Bound<'_, PyDict> = PyDict::new_bound(py); - - let num_fields = postgres_types::private::read_be_i32(buf).map_err(|err| { - RustPSQLDriverError::RustToPyValueConversionError(format!( - "Cannot read bytes data from PostgreSQL: {err}" - )) - })?; - if num_fields as usize != fields.len() { - return Err(RustPSQLDriverError::RustToPyValueConversionError(format!( - "invalid field count: {} vs {}", - num_fields, - fields.len() - ))); - } - - for field in fields { - let oid = postgres_types::private::read_be_i32(buf).map_err(|err| { - RustPSQLDriverError::RustToPyValueConversionError(format!( - "Cannot read bytes data from PostgreSQL: {err}" - )) - })? as u32; - - if oid != field.type_().oid() { - return Err(RustPSQLDriverError::RustToPyValueConversionError( - "unexpected OID".into(), - )); - } - - match field.type_().kind() { - Kind::Simple | Kind::Array(_) => { - result_py_dict.set_item( - field.name(), - postgres_bytes_to_py(py, field.type_(), buf, false)?.to_object(py), - )?; - } - Kind::Enum(_) => { - result_py_dict.set_item( - field.name(), - postgres_bytes_to_py(py, &Type::VARCHAR, buf, false)?.to_object(py), - )?; - } - _ => { - let (_, tail) = buf.split_at(4_usize); - *buf = tail; - result_py_dict.set_item( - field.name(), - raw_bytes_data_process(py, buf, field.name(), field.type_(), custom_decoders)? - .to_object(py), - )?; - } - } - } - - Ok(result_py_dict.to_object(py)) -} - -/// Process raw bytes from `PostgreSQL`. -/// -/// # Errors -/// -/// May return Err Result if cannot convert postgres -/// type into rust one. -pub fn raw_bytes_data_process( - py: Python<'_>, - raw_bytes_data: &mut &[u8], - column_name: &str, - column_type: &Type, - custom_decoders: &Option>, -) -> RustPSQLDriverPyResult> { - if let Some(custom_decoders) = custom_decoders { - let py_encoder_func = custom_decoders - .bind(py) - .get_item(column_name.to_lowercase()); - - if let Ok(Some(py_encoder_func)) = py_encoder_func { - return Ok(py_encoder_func - .call((raw_bytes_data.to_vec(),), None)? - .unbind()); - } - } - - match column_type.kind() { - Kind::Simple | Kind::Array(_) => { - postgres_bytes_to_py(py, column_type, raw_bytes_data, true) - } - Kind::Composite(fields) => { - composite_postgres_to_py(py, fields, raw_bytes_data, custom_decoders) - } - Kind::Enum(_) => postgres_bytes_to_py(py, &Type::VARCHAR, raw_bytes_data, true), - _ => Err(RustPSQLDriverError::RustToPyValueConversionError( - column_type.to_string(), - )), - } -} - -/// Convert type from postgres to python type. -/// -/// # Errors -/// -/// May return Err Result if cannot convert postgres -/// type into rust one. -pub fn postgres_to_py( - py: Python<'_>, - row: &Row, - column: &Column, - column_i: usize, - custom_decoders: &Option>, -) -> RustPSQLDriverPyResult> { - let raw_bytes_data = row.col_buffer(column_i); - if let Some(mut raw_bytes_data) = raw_bytes_data { - return raw_bytes_data_process( - py, - &mut raw_bytes_data, - column.name(), - column.type_(), - custom_decoders, - ); - } - Ok(py.None()) -} - -/// Convert python List of Dict type or just Dict into serde `Value`. -/// -/// # Errors -/// May return error if cannot convert Python type into Rust one. -#[allow(clippy::needless_pass_by_value)] -pub fn build_serde_value(value: Py) -> RustPSQLDriverPyResult { - Python::with_gil(|gil| { - let bind_value = value.bind(gil); - if bind_value.is_instance_of::() { - let mut result_vec: Vec = vec![]; - - let params = bind_value.extract::>>()?; - - for inner in params { - let inner_bind = inner.bind(gil); - if inner_bind.is_instance_of::() { - let python_dto = py_to_rust(inner_bind)?; - result_vec.push(python_dto.to_serde_value()?); - } else if inner_bind.is_instance_of::() { - let serde_value = build_serde_value(inner)?; - result_vec.push(serde_value); - } else { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "PyJSON must have dicts.".to_string(), - )); - } - } - Ok(json!(result_vec)) - } else if bind_value.is_instance_of::() { - return py_to_rust(bind_value)?.to_serde_value(); - } else { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "PyJSON must be dict value.".to_string(), - )); - } - }) -} - -/// Convert serde `Value` into Python object. -/// # Errors -/// May return Err Result if cannot add new value to Python Dict. -pub fn build_python_from_serde_value( - py: Python<'_>, - value: Value, -) -> RustPSQLDriverPyResult> { - match value { - Value::Array(massive) => { - let mut result_vec: Vec> = vec![]; - - for single_record in massive { - result_vec.push(build_python_from_serde_value(py, single_record)?); - } - - Ok(result_vec.to_object(py)) - } - Value::Object(mapping) => { - let py_dict = PyDict::new_bound(py); - - for (key, value) in mapping { - py_dict.set_item( - build_python_from_serde_value(py, Value::String(key))?, - build_python_from_serde_value(py, value)?, - )?; - } - - Ok(py_dict.to_object(py)) - } - Value::Bool(boolean) => Ok(boolean.to_object(py)), - Value::Number(number) => { - if number.is_f64() { - Ok(number.as_f64().to_object(py)) - } else if number.is_i64() { - Ok(number.as_i64().to_object(py)) - } else { - Ok(number.as_u64().to_object(py)) - } - } - Value::String(string) => Ok(string.to_object(py)), - Value::Null => Ok(py.None()), - } -} - -/// Convert Python sequence to Rust vector. -/// Also it checks that sequence has set/list/tuple type. -/// -/// # Errors -/// -/// May return error if cannot convert Python type into Rust one. -/// May return error if parameters type isn't correct. -fn py_sequence_to_rust(bind_parameters: &Bound) -> RustPSQLDriverPyResult>> { - let mut coord_values_sequence_vec: Vec> = vec![]; - - if bind_parameters.is_instance_of::() { - let bind_pyset_parameters = bind_parameters.downcast::().unwrap(); - - for one_parameter in bind_pyset_parameters { - let extracted_parameter = one_parameter.extract::>().map_err(|_| { - RustPSQLDriverError::PyToRustValueConversionError( - format!("Error on sequence type extraction, please use correct list/tuple/set, {bind_parameters}") - ) - })?; - coord_values_sequence_vec.push(extracted_parameter); - } - } else if bind_parameters.is_instance_of::() - | bind_parameters.is_instance_of::() - { - coord_values_sequence_vec = bind_parameters.extract::>>().map_err(|_| { - RustPSQLDriverError::PyToRustValueConversionError( - format!("Error on sequence type extraction, please use correct list/tuple/set, {bind_parameters}") - ) - })?; - } else { - return Err(RustPSQLDriverError::PyToRustValueConversionError(format!( - "Invalid sequence type, please use list/tuple/set, {bind_parameters}" - ))); - }; - - Ok::>, RustPSQLDriverError>(coord_values_sequence_vec) -} - -/// Convert two python parameters(x and y) to Coord from `geo_type`. -/// Also it checks that passed values is int or float. -/// -/// # Errors -/// -/// May return error if cannot convert Python type into Rust one. -/// May return error if parameters type isn't correct. -fn convert_py_to_rust_coord_values(parameters: Vec>) -> RustPSQLDriverPyResult> { - Python::with_gil(|gil| { - let mut coord_values_vec: Vec = vec![]; - - for one_parameter in parameters { - let parameter_bind = one_parameter.bind(gil); - - if !parameter_bind.is_instance_of::() - & !parameter_bind.is_instance_of::() - { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "Incorrect types of coordinate values. It must be int or float".into(), - )); - } - - let python_dto = py_to_rust(parameter_bind)?; - match python_dto { - PythonDTO::PyIntI16(pyint) => coord_values_vec.push(f64::from(pyint)), - PythonDTO::PyIntI32(pyint) => coord_values_vec.push(f64::from(pyint)), - PythonDTO::PyIntU32(pyint) => coord_values_vec.push(f64::from(pyint)), - PythonDTO::PyFloat32(pyfloat) => coord_values_vec.push(f64::from(pyfloat)), - PythonDTO::PyFloat64(pyfloat) => coord_values_vec.push(pyfloat), - PythonDTO::PyIntI64(_) | PythonDTO::PyIntU64(_) => { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "Not implemented this type yet".into(), - )) - } - _ => { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "Incorrect types of coordinate values. It must be int or float".into(), - )) - } - }; - } - - Ok::, RustPSQLDriverError>(coord_values_vec) - }) -} - -/// Convert Python values with coordinates into vector of Coord's for building Geo types later. -/// -/// Passed parameter can be either a list or a tuple or a set. -/// Inside this parameter may be multiple list/tuple/set with int/float or only int/float values flat. -/// We parse every parameter from python object and make from them Coord's. -/// Additionally it checks for correct length of coordinates parsed from Python values. -/// -/// # Errors -/// -/// May return error if cannot convert Python type into Rust one. -/// May return error if parsed number of coordinates is not expected by allowed length. -#[allow(clippy::needless_pass_by_value)] -pub fn build_geo_coords( - py_parameters: Py, - allowed_length_option: Option, -) -> RustPSQLDriverPyResult> { - let mut result_vec: Vec = vec![]; - - result_vec = Python::with_gil(|gil| { - let bind_py_parameters = py_parameters.bind(gil); - let parameters = py_sequence_to_rust(bind_py_parameters)?; - - let first_inner_bind_py_parameters = parameters[0].bind(gil); - if first_inner_bind_py_parameters.is_instance_of::() - | first_inner_bind_py_parameters.is_instance_of::() - { - if parameters.len() % 2 != 0 { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "Length of coordinates that passed in flat structure must be a multiple of 2" - .into(), - )); - } - - for (pair_first_inner, pair_second_inner) in parameters.into_iter().tuples() { - let coord_values = - convert_py_to_rust_coord_values(vec![pair_first_inner, pair_second_inner])?; - result_vec.push(coord! {x: coord_values[0], y: coord_values[1]}); - } - } else if first_inner_bind_py_parameters.is_instance_of::() - | first_inner_bind_py_parameters.is_instance_of::() - | first_inner_bind_py_parameters.is_instance_of::() - { - for pair_inner_parameters in parameters { - let bind_pair_inner_parameters = pair_inner_parameters.bind(gil); - let pair_py_inner_parameters = py_sequence_to_rust(bind_pair_inner_parameters)?; - - if pair_py_inner_parameters.len() != 2 { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "Inner parameters must be pair(list/tuple/set) of int/float values".into(), - )); - } - - let coord_values = convert_py_to_rust_coord_values(pair_py_inner_parameters)?; - result_vec.push(coord! {x: coord_values[0], y: coord_values[1]}); - } - } else { - return Err(RustPSQLDriverError::PyToRustValueConversionError( - "Inner coordinates must be passed as pairs of int/float in list/tuple/set or as flat structure with int/float values".into(), - )); - }; - Ok::, RustPSQLDriverError>(result_vec) - })?; - - let number_of_coords = result_vec.len(); - let allowed_length = allowed_length_option.unwrap_or_default(); - - if (allowed_length != 0) & (number_of_coords != allowed_length) { - return Err(RustPSQLDriverError::PyToRustValueConversionError(format!( - "Invalid number of coordinates for this geo type, allowed {allowed_length}, got: {number_of_coords}" - ))); - } - - Ok(result_vec) -} - -/// Convert flat Python values with coordinates into vector of Geo values for building Geo types later. -/// -/// Passed parameter can be either a list or a tuple or a set with elements. -/// We parse every parameter from python object and prepare them for making geo type. -/// Additionally it checks for correct length of coordinates parsed from Python values. -/// -/// # Errors -/// -/// May return error if cannot convert Python type into Rust one. -/// May return error if parsed number of coordinates is not expected by allowed length. -#[allow(clippy::needless_pass_by_value)] -pub fn build_flat_geo_coords( - py_parameters: Py, - allowed_length_option: Option, -) -> RustPSQLDriverPyResult> { - Python::with_gil(|gil| { - let allowed_length = allowed_length_option.unwrap_or_default(); - - let bind_py_parameters = py_parameters.bind(gil); - let parameters = py_sequence_to_rust(bind_py_parameters)?; - let parameters_length = parameters.len(); - - if (allowed_length != 0) & (parameters.len() != allowed_length) { - return Err(RustPSQLDriverError::PyToRustValueConversionError(format!( - "Invalid number of values for this geo type, allowed {allowed_length}, got: {parameters_length}" - ))); - }; - - let result_vec = convert_py_to_rust_coord_values(parameters)?; - - let number_of_coords = result_vec.len(); - if (allowed_length != 0) & (number_of_coords != allowed_length) { - return Err(RustPSQLDriverError::PyToRustValueConversionError(format!( - "Invalid number of values for this geo type, allowed {allowed_length}, got: {parameters_length}" - ))); - }; - - Ok::, RustPSQLDriverError>(result_vec) - }) -} diff --git a/src/additional_types.rs b/src/value_converter/additional_types.rs similarity index 100% rename from src/additional_types.rs rename to src/value_converter/additional_types.rs diff --git a/src/value_converter/consts.rs b/src/value_converter/consts.rs new file mode 100644 index 00000000..40fa932b --- /dev/null +++ b/src/value_converter/consts.rs @@ -0,0 +1,37 @@ +use once_cell::sync::Lazy; +use postgres_types::ToSql; +use std::{collections::HashMap, sync::RwLock}; + +use pyo3::{ + sync::GILOnceCell, + types::{PyAnyMethods, PyType}, + Bound, Py, PyResult, Python, +}; + +pub static DECIMAL_CLS: GILOnceCell> = GILOnceCell::new(); +pub static TIMEDELTA_CLS: GILOnceCell> = GILOnceCell::new(); +pub static KWARGS_QUERYSTRINGS: Lazy)>>> = + Lazy::new(|| RwLock::new(Default::default())); + +pub fn get_decimal_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { + DECIMAL_CLS + .get_or_try_init(py, || { + let type_object = py.import("decimal")?.getattr("Decimal")?.downcast_into()?; + Ok(type_object.unbind()) + }) + .map(|ty| ty.bind(py)) +} + +pub fn get_timedelta_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { + TIMEDELTA_CLS + .get_or_try_init(py, || { + let type_object = py + .import("datetime")? + .getattr("timedelta")? + .downcast_into()?; + Ok(type_object.unbind()) + }) + .map(|ty| ty.bind(py)) +} + +pub type QueryParameter = (dyn ToSql + Sync); diff --git a/src/value_converter/funcs/from_python.rs b/src/value_converter/funcs/from_python.rs new file mode 100644 index 00000000..4fe73290 --- /dev/null +++ b/src/value_converter/funcs/from_python.rs @@ -0,0 +1,957 @@ +use chrono::{self, DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime, TimeZone}; +use chrono_tz::Tz; +use geo_types::{coord, Coord}; +use itertools::Itertools; +use pg_interval::Interval; +use postgres_array::{Array, Dimension}; +use rust_decimal::Decimal; +use serde_json::{json, Map, Value}; +use std::net::IpAddr; +use uuid::Uuid; + +use pyo3::{ + types::{ + PyAnyMethods, PyBool, PyBytes, PyDate, PyDateTime, PyDelta, PyDict, PyDictMethods, PyFloat, + PyInt, PyList, PyMapping, PySequence, PySet, PyString, PyTime, PyTuple, PyTypeMethods, + }, + Bound, Py, PyAny, Python, +}; + +use crate::{ + exceptions::rust_errors::{RustPSQLDriverError, RustPSQLDriverPyResult}, + extra_types::{self}, + value_converter::{ + consts::KWARGS_QUERYSTRINGS, models::dto::PythonDTO, + utils::extract_value_from_python_object_or_raise, + }, +}; + +/// Convert single python parameter to `PythonDTO` enum. +/// +/// # Errors +/// +/// May return Err Result if python type doesn't have support yet +/// or value of the type is incorrect. +#[allow(clippy::too_many_lines)] +pub fn py_to_rust(parameter: &pyo3::Bound<'_, PyAny>) -> RustPSQLDriverPyResult { + if parameter.is_none() { + return Ok(PythonDTO::PyNone); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyCustomType( + parameter.extract::()?.inner(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyBool(parameter.extract::()?)); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyBytes(parameter.extract::>()?)); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyText( + parameter.extract::()?.inner(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyVarChar( + parameter.extract::()?.inner(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyString(parameter.extract::()?)); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyFloat64(parameter.extract::()?)); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyFloat32( + parameter + .extract::()? + .retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyFloat64( + parameter + .extract::()? + .retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyIntI16( + parameter + .extract::()? + .retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyIntI32( + parameter + .extract::()? + .retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyIntI64( + parameter.extract::()?.retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyMoney( + parameter.extract::()?.retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyIntI32(parameter.extract::()?)); + } + + if parameter.is_instance_of::() { + let timestamp_tz = parameter.extract::>(); + if let Ok(pydatetime_tz) = timestamp_tz { + return Ok(PythonDTO::PyDateTimeTz(pydatetime_tz)); + } + + let timestamp_no_tz = parameter.extract::(); + if let Ok(pydatetime_no_tz) = timestamp_no_tz { + return Ok(PythonDTO::PyDateTime(pydatetime_no_tz)); + } + + let timestamp_tz = extract_datetime_from_python_object_attrs(parameter); + if let Ok(pydatetime_tz) = timestamp_tz { + return Ok(PythonDTO::PyDateTimeTz(pydatetime_tz)); + } + + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "Can not convert you datetime to rust type".into(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyDate(parameter.extract::()?)); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyTime(parameter.extract::()?)); + } + + if parameter.is_instance_of::() { + let duration = parameter.extract::()?; + if let Some(interval) = Interval::from_duration(duration) { + return Ok(PythonDTO::PyInterval(interval)); + } + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "Cannot convert timedelta from Python to inner Rust type.".to_string(), + )); + } + + if parameter.is_instance_of::() | parameter.is_instance_of::() { + return Ok(PythonDTO::PyArray(py_sequence_into_postgres_array( + parameter, + )?)); + } + + if parameter.is_instance_of::() { + let dict = parameter.downcast::().map_err(|error| { + RustPSQLDriverError::PyToRustValueConversionError(format!( + "Can't cast to inner dict: {error}" + )) + })?; + + let mut serde_map: Map = Map::new(); + + for dict_item in dict.items() { + let py_list = dict_item.downcast::().map_err(|error| { + RustPSQLDriverError::PyToRustValueConversionError(format!( + "Cannot cast to list: {error}" + )) + })?; + + let key = py_list.get_item(0)?.extract::()?; + let value = py_to_rust(&py_list.get_item(1)?)?; + + serde_map.insert(key, value.to_serde_value()?); + } + + return Ok(PythonDTO::PyJsonb(Value::Object(serde_map))); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyJsonb( + parameter.extract::()?.inner().clone(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyJson( + parameter.extract::()?.inner().clone(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyMacAddr6( + parameter.extract::()?.inner(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyMacAddr8( + parameter.extract::()?.inner(), + )); + } + + if parameter.get_type().name()? == "UUID" { + return Ok(PythonDTO::PyUUID(Uuid::parse_str( + parameter.str()?.extract::<&str>()?, + )?)); + } + + if parameter.get_type().name()? == "decimal.Decimal" + || parameter.get_type().name()? == "Decimal" + { + return Ok(PythonDTO::PyDecimal(Decimal::from_str_exact( + parameter.str()?.extract::<&str>()?, + )?)); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyPoint( + parameter.extract::()?.retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyBox( + parameter.extract::()?.retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyPath( + parameter.extract::()?.retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyLine( + parameter.extract::()?.retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyLineSegment( + parameter + .extract::()? + .retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyCircle( + parameter.extract::()?.retrieve_value(), + )); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return parameter + .extract::()? + ._convert_to_python_dto(); + } + + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyPgVector( + parameter.extract::()?.inner_value(), + )); + } + + if let Ok(id_address) = parameter.extract::() { + return Ok(PythonDTO::PyIpAddress(id_address)); + } + + // It's used for Enum. + // If StrEnum is used on Python side, + // we simply stop at the `is_instance_of::``. + if let Ok(value_attr) = parameter.getattr("value") { + if let Ok(possible_string) = value_attr.extract::() { + return Ok(PythonDTO::PyString(possible_string)); + } + } + + Err(RustPSQLDriverError::PyToRustValueConversionError(format!( + "Can not covert you type {parameter} into inner one", + ))) +} + +/// Extract a timezone-aware datetime from a Python object. +/// This function retrieves various datetime components (`year`, `month`, `day`, etc.) +/// from a Python object and constructs a `DateTime` +/// +/// # Errors +/// This function will return `Err` in the following cases: +/// - The Python object does not contain or support one or more required datetime attributes +/// - The retrieved values are invalid for constructing a date, time, or datetime (e.g., invalid month or day) +/// - The timezone information (`tzinfo`) is not available or cannot be parsed +/// - The resulting datetime is ambiguous or invalid (e.g., due to DST transitions) +fn extract_datetime_from_python_object_attrs( + parameter: &pyo3::Bound<'_, PyAny>, +) -> Result, RustPSQLDriverError> { + let year = extract_value_from_python_object_or_raise::(parameter, "year")?; + let month = extract_value_from_python_object_or_raise::(parameter, "month")?; + let day = extract_value_from_python_object_or_raise::(parameter, "day")?; + let hour = extract_value_from_python_object_or_raise::(parameter, "hour")?; + let minute = extract_value_from_python_object_or_raise::(parameter, "minute")?; + let second = extract_value_from_python_object_or_raise::(parameter, "second")?; + let microsecond = extract_value_from_python_object_or_raise::(parameter, "microsecond")?; + + let date = NaiveDate::from_ymd_opt(year, month, day) + .ok_or_else(|| RustPSQLDriverError::PyToRustValueConversionError("Invalid date".into()))?; + let time = NaiveTime::from_hms_micro_opt(hour, minute, second, microsecond) + .ok_or_else(|| RustPSQLDriverError::PyToRustValueConversionError("Invalid time".into()))?; + let naive_datetime = NaiveDateTime::new(date, time); + + let raw_timestamp_tz = parameter + .getattr("tzinfo") + .ok() + .and_then(|tzinfo| tzinfo.getattr("key").ok()) + .and_then(|key| key.extract::().ok()) + .ok_or_else(|| { + RustPSQLDriverError::PyToRustValueConversionError("Invalid timezone info".into()) + })?; + + let fixed_offset_datetime = raw_timestamp_tz + .parse::() + .map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError("Failed to parse TZ".into()) + })? + .from_local_datetime(&naive_datetime) + .single() + .ok_or_else(|| { + RustPSQLDriverError::PyToRustValueConversionError( + "Ambiguous or invalid datetime".into(), + ) + })? + .fixed_offset(); + + Ok(fixed_offset_datetime) +} + +/// Convert Sequence from Python into Postgres ARRAY. +/// +/// # Errors +/// +/// May return Err Result if cannot convert at least one element. +#[allow(clippy::cast_possible_truncation)] +#[allow(clippy::cast_possible_wrap)] +pub fn py_sequence_into_postgres_array( + parameter: &Bound, +) -> RustPSQLDriverPyResult> { + let mut py_seq = parameter + .downcast::() + .map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError( + "PostgreSQL ARRAY type can be made only from python Sequence".into(), + ) + })? + .clone(); + + let mut dimensions: Vec = vec![]; + let mut continue_iteration = true; + + while continue_iteration { + dimensions.push(Dimension { + len: py_seq.len()? as i32, + lower_bound: 1, + }); + + let first_seq_elem = py_seq.try_iter()?.next(); + match first_seq_elem { + Some(first_seq_elem) => { + if let Ok(first_seq_elem) = first_seq_elem { + // Check for the string because it's sequence too, + // and in the most cases it should be array type, not new dimension. + if first_seq_elem.is_instance_of::() { + continue_iteration = false; + continue; + } + let possible_inner_seq = first_seq_elem.downcast::(); + + match possible_inner_seq { + Ok(possible_inner_seq) => { + py_seq = possible_inner_seq.clone(); + } + Err(_) => continue_iteration = false, + } + } + } + None => { + continue_iteration = false; + } + } + } + + let array_data = py_sequence_into_flat_vec(parameter)?; + match postgres_array::Array::from_parts_no_panic(array_data, dimensions) { + Ok(result_array) => Ok(result_array), + Err(err) => Err(RustPSQLDriverError::PyToRustValueConversionError(format!( + "Cannot convert python sequence to PostgreSQL ARRAY, error - {err}" + ))), + } +} + +/// Convert Sequence from Python (except String) into flat vec. +/// +/// # Errors +/// May return Err Result if cannot convert element into Rust one. +pub fn py_sequence_into_flat_vec( + parameter: &Bound, +) -> RustPSQLDriverPyResult> { + let py_seq = parameter.downcast::().map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError( + "PostgreSQL ARRAY type can be made only from python Sequence".into(), + ) + })?; + + let mut final_vec: Vec = vec![]; + + for seq_elem in py_seq.try_iter()? { + let ok_seq_elem = seq_elem?; + + // Check for the string because it's sequence too, + // and in the most cases it should be array type, not new dimension. + if ok_seq_elem.is_instance_of::() { + final_vec.push(py_to_rust(&ok_seq_elem)?); + continue; + } + + let possible_next_seq = ok_seq_elem.downcast::(); + + if let Ok(next_seq) = possible_next_seq { + let mut next_vec = py_sequence_into_flat_vec(next_seq)?; + final_vec.append(&mut next_vec); + } else { + final_vec.push(py_to_rust(&ok_seq_elem)?); + continue; + } + } + + Ok(final_vec) +} + +/// Convert parameters come from python. +/// +/// Parameters for `execute()` method can be either +/// a list or a tuple or a set. +/// +/// We parse every parameter from python object and return +/// Vector of out `PythonDTO`. +/// +/// # Errors +/// +/// May return Err Result if can't convert python object. +#[allow(clippy::needless_pass_by_value)] +pub fn convert_parameters_and_qs( + querystring: String, + parameters: Option>, +) -> RustPSQLDriverPyResult<(String, Vec)> { + let Some(parameters) = parameters else { + return Ok((querystring, vec![])); + }; + + let res = Python::with_gil(|gil| { + let params = parameters.extract::>>(gil).map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError( + "Cannot convert you parameters argument into Rust type, please use List/Tuple" + .into(), + ) + }); + if let Ok(params) = params { + return Ok((querystring, convert_seq_parameters(params)?)); + } + + let kw_params = parameters.downcast_bound::(gil); + if let Ok(kw_params) = kw_params { + return convert_kwargs_parameters(kw_params, &querystring); + } + + Err(RustPSQLDriverError::PyToRustValueConversionError( + "Parameters must be sequence or mapping".into(), + )) + })?; + + Ok(res) +} + +pub fn convert_kwargs_parameters<'a>( + kw_params: &Bound<'_, PyMapping>, + querystring: &'a str, +) -> RustPSQLDriverPyResult<(String, Vec)> { + let mut result_vec: Vec = vec![]; + let (changed_string, params_names) = parse_kwargs_qs(querystring); + + for param_name in params_names { + match kw_params.get_item(¶m_name) { + Ok(param) => result_vec.push(py_to_rust(¶m)?), + Err(_) => { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + format!("Cannot find parameter with name <{param_name}> in parameters").into(), + )) + } + } + } + + Ok((changed_string, result_vec)) +} + +pub fn convert_seq_parameters( + seq_params: Vec>, +) -> RustPSQLDriverPyResult> { + let mut result_vec: Vec = vec![]; + Python::with_gil(|gil| { + for parameter in seq_params { + result_vec.push(py_to_rust(parameter.bind(gil))?); + } + Ok::<(), RustPSQLDriverError>(()) + })?; + + Ok(result_vec) +} + +/// Convert python List of Dict type or just Dict into serde `Value`. +/// +/// # Errors +/// May return error if cannot convert Python type into Rust one. +#[allow(clippy::needless_pass_by_value)] +pub fn build_serde_value(value: Py) -> RustPSQLDriverPyResult { + Python::with_gil(|gil| { + let bind_value = value.bind(gil); + if bind_value.is_instance_of::() { + let mut result_vec: Vec = vec![]; + + let params = bind_value.extract::>>()?; + + for inner in params { + let inner_bind = inner.bind(gil); + if inner_bind.is_instance_of::() { + let python_dto = py_to_rust(inner_bind)?; + result_vec.push(python_dto.to_serde_value()?); + } else if inner_bind.is_instance_of::() { + let serde_value = build_serde_value(inner)?; + result_vec.push(serde_value); + } else { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "PyJSON must have dicts.".to_string(), + )); + } + } + Ok(json!(result_vec)) + } else if bind_value.is_instance_of::() { + return py_to_rust(bind_value)?.to_serde_value(); + } else { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "PyJSON must be dict value.".to_string(), + )); + } + }) +} + +/// Convert two python parameters(x and y) to Coord from `geo_type`. +/// Also it checks that passed values is int or float. +/// +/// # Errors +/// +/// May return error if cannot convert Python type into Rust one. +/// May return error if parameters type isn't correct. +fn convert_py_to_rust_coord_values(parameters: Vec>) -> RustPSQLDriverPyResult> { + Python::with_gil(|gil| { + let mut coord_values_vec: Vec = vec![]; + + for one_parameter in parameters { + let parameter_bind = one_parameter.bind(gil); + + if !parameter_bind.is_instance_of::() + & !parameter_bind.is_instance_of::() + { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "Incorrect types of coordinate values. It must be int or float".into(), + )); + } + + let python_dto = py_to_rust(parameter_bind)?; + match python_dto { + PythonDTO::PyIntI16(pyint) => coord_values_vec.push(f64::from(pyint)), + PythonDTO::PyIntI32(pyint) => coord_values_vec.push(f64::from(pyint)), + PythonDTO::PyIntU32(pyint) => coord_values_vec.push(f64::from(pyint)), + PythonDTO::PyFloat32(pyfloat) => coord_values_vec.push(f64::from(pyfloat)), + PythonDTO::PyFloat64(pyfloat) => coord_values_vec.push(pyfloat), + PythonDTO::PyIntI64(_) | PythonDTO::PyIntU64(_) => { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "Not implemented this type yet".into(), + )) + } + _ => { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "Incorrect types of coordinate values. It must be int or float".into(), + )) + } + }; + } + + Ok::, RustPSQLDriverError>(coord_values_vec) + }) +} + +/// Convert Python values with coordinates into vector of Coord's for building Geo types later. +/// +/// Passed parameter can be either a list or a tuple or a set. +/// Inside this parameter may be multiple list/tuple/set with int/float or only int/float values flat. +/// We parse every parameter from python object and make from them Coord's. +/// Additionally it checks for correct length of coordinates parsed from Python values. +/// +/// # Errors +/// +/// May return error if cannot convert Python type into Rust one. +/// May return error if parsed number of coordinates is not expected by allowed length. +#[allow(clippy::needless_pass_by_value)] +pub fn build_geo_coords( + py_parameters: Py, + allowed_length_option: Option, +) -> RustPSQLDriverPyResult> { + let mut result_vec: Vec = vec![]; + + result_vec = Python::with_gil(|gil| { + let bind_py_parameters = py_parameters.bind(gil); + let parameters = py_sequence_to_rust(bind_py_parameters)?; + + let first_inner_bind_py_parameters = parameters[0].bind(gil); + if first_inner_bind_py_parameters.is_instance_of::() + | first_inner_bind_py_parameters.is_instance_of::() + { + if parameters.len() % 2 != 0 { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "Length of coordinates that passed in flat structure must be a multiple of 2" + .into(), + )); + } + + for (pair_first_inner, pair_second_inner) in parameters.into_iter().tuples() { + let coord_values = + convert_py_to_rust_coord_values(vec![pair_first_inner, pair_second_inner])?; + result_vec.push(coord! {x: coord_values[0], y: coord_values[1]}); + } + } else if first_inner_bind_py_parameters.is_instance_of::() + | first_inner_bind_py_parameters.is_instance_of::() + | first_inner_bind_py_parameters.is_instance_of::() + { + for pair_inner_parameters in parameters { + let bind_pair_inner_parameters = pair_inner_parameters.bind(gil); + let pair_py_inner_parameters = py_sequence_to_rust(bind_pair_inner_parameters)?; + + if pair_py_inner_parameters.len() != 2 { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "Inner parameters must be pair(list/tuple/set) of int/float values".into(), + )); + } + + let coord_values = convert_py_to_rust_coord_values(pair_py_inner_parameters)?; + result_vec.push(coord! {x: coord_values[0], y: coord_values[1]}); + } + } else { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "Inner coordinates must be passed as pairs of int/float in list/tuple/set or as flat structure with int/float values".into(), + )); + }; + Ok::, RustPSQLDriverError>(result_vec) + })?; + + let number_of_coords = result_vec.len(); + let allowed_length = allowed_length_option.unwrap_or_default(); + + if (allowed_length != 0) & (number_of_coords != allowed_length) { + return Err(RustPSQLDriverError::PyToRustValueConversionError(format!( + "Invalid number of coordinates for this geo type, allowed {allowed_length}, got: {number_of_coords}" + ))); + } + + Ok(result_vec) +} + +/// Convert flat Python values with coordinates into vector of Geo values for building Geo types later. +/// +/// Passed parameter can be either a list or a tuple or a set with elements. +/// We parse every parameter from python object and prepare them for making geo type. +/// Additionally it checks for correct length of coordinates parsed from Python values. +/// +/// # Errors +/// +/// May return error if cannot convert Python type into Rust one. +/// May return error if parsed number of coordinates is not expected by allowed length. +#[allow(clippy::needless_pass_by_value)] +pub fn build_flat_geo_coords( + py_parameters: Py, + allowed_length_option: Option, +) -> RustPSQLDriverPyResult> { + Python::with_gil(|gil| { + let allowed_length = allowed_length_option.unwrap_or_default(); + + let bind_py_parameters = py_parameters.bind(gil); + let parameters = py_sequence_to_rust(bind_py_parameters)?; + let parameters_length = parameters.len(); + + if (allowed_length != 0) & (parameters.len() != allowed_length) { + return Err(RustPSQLDriverError::PyToRustValueConversionError(format!( + "Invalid number of values for this geo type, allowed {allowed_length}, got: {parameters_length}" + ))); + }; + + let result_vec = convert_py_to_rust_coord_values(parameters)?; + + let number_of_coords = result_vec.len(); + if (allowed_length != 0) & (number_of_coords != allowed_length) { + return Err(RustPSQLDriverError::PyToRustValueConversionError(format!( + "Invalid number of values for this geo type, allowed {allowed_length}, got: {parameters_length}" + ))); + }; + + Ok::, RustPSQLDriverError>(result_vec) + }) +} + +/// Convert Python sequence to Rust vector. +/// Also it checks that sequence has set/list/tuple type. +/// +/// # Errors +/// +/// May return error if cannot convert Python type into Rust one. +/// May return error if parameters type isn't correct. +fn py_sequence_to_rust(bind_parameters: &Bound) -> RustPSQLDriverPyResult>> { + let mut coord_values_sequence_vec: Vec> = vec![]; + + if bind_parameters.is_instance_of::() { + let bind_pyset_parameters = bind_parameters.downcast::().unwrap(); + + for one_parameter in bind_pyset_parameters { + let extracted_parameter = one_parameter.extract::>().map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError( + format!("Error on sequence type extraction, please use correct list/tuple/set, {bind_parameters}") + ) + })?; + coord_values_sequence_vec.push(extracted_parameter); + } + } else if bind_parameters.is_instance_of::() + | bind_parameters.is_instance_of::() + { + coord_values_sequence_vec = bind_parameters.extract::>>().map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError( + format!("Error on sequence type extraction, please use correct list/tuple/set, {bind_parameters}") + ) + })?; + } else { + return Err(RustPSQLDriverError::PyToRustValueConversionError(format!( + "Invalid sequence type, please use list/tuple/set, {bind_parameters}" + ))); + }; + + Ok::>, RustPSQLDriverError>(coord_values_sequence_vec) +} + +fn parse_kwargs_qs(querystring: &str) -> (String, Vec) { + let re = regex::Regex::new(r"\$\(([^)]+)\)p").unwrap(); + + { + let kq_read = KWARGS_QUERYSTRINGS.read().unwrap(); + let qs = kq_read.get(querystring); + + if let Some(qs) = qs { + return qs.clone(); + } + }; + + let mut counter = 0; + let mut sequence = Vec::new(); + + let result = re.replace_all(querystring, |caps: ®ex::Captures| { + let account_id = caps[1].to_string(); + + sequence.push(account_id.clone()); + counter += 1; + + format!("${}", &counter) + }); + + let mut kq_write = KWARGS_QUERYSTRINGS.write().unwrap(); + kq_write.insert( + querystring.to_string(), + (result.clone().into(), sequence.clone()), + ); + (result.into(), sequence) +} diff --git a/src/value_converter/funcs/mod.rs b/src/value_converter/funcs/mod.rs new file mode 100644 index 00000000..4db4cd38 --- /dev/null +++ b/src/value_converter/funcs/mod.rs @@ -0,0 +1,2 @@ +pub mod from_python; +pub mod to_python; diff --git a/src/value_converter/funcs/to_python.rs b/src/value_converter/funcs/to_python.rs new file mode 100644 index 00000000..e65a0085 --- /dev/null +++ b/src/value_converter/funcs/to_python.rs @@ -0,0 +1,711 @@ +use chrono::{self, DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; +use pg_interval::Interval; +use postgres_array::{Array, Dimension}; +use postgres_types::{Field, FromSql, Kind, Type}; +use rust_decimal::Decimal; +use serde_json::Value; +use std::net::IpAddr; +use tokio_postgres::{Column, Row}; +use uuid::Uuid; + +use pyo3::{ + types::{ + PyAnyMethods, PyBytes, PyDict, PyDictMethods, PyList, PyListMethods, PySet, PyString, + PyTuple, + }, + Bound, IntoPy, Py, PyAny, Python, ToPyObject, +}; + +use crate::{ + exceptions::rust_errors::{RustPSQLDriverError, RustPSQLDriverPyResult}, + value_converter::{ + additional_types::{ + Circle, Line, RustLineSegment, RustLineString, RustMacAddr6, RustMacAddr8, RustPoint, + RustRect, + }, + consts::KWARGS_QUERYSTRINGS, + models::{ + decimal::InnerDecimal, interval::InnerInterval, serde_value::InternalSerdeValue, + uuid::InternalUuid, + }, + }, +}; +use pgvector::Vector as PgVector; + +/// Convert serde `Value` into Python object. +/// # Errors +/// May return Err Result if cannot add new value to Python Dict. +pub fn build_python_from_serde_value( + py: Python<'_>, + value: Value, +) -> RustPSQLDriverPyResult> { + match value { + Value::Array(massive) => { + let mut result_vec: Vec> = vec![]; + + for single_record in massive { + result_vec.push(build_python_from_serde_value(py, single_record)?); + } + + Ok(result_vec.to_object(py)) + } + Value::Object(mapping) => { + let py_dict = PyDict::new_bound(py); + + for (key, value) in mapping { + py_dict.set_item( + build_python_from_serde_value(py, Value::String(key))?, + build_python_from_serde_value(py, value)?, + )?; + } + + Ok(py_dict.to_object(py)) + } + Value::Bool(boolean) => Ok(boolean.to_object(py)), + Value::Number(number) => { + if number.is_f64() { + Ok(number.as_f64().to_object(py)) + } else if number.is_i64() { + Ok(number.as_i64().to_object(py)) + } else { + Ok(number.as_u64().to_object(py)) + } + } + Value::String(string) => Ok(string.to_object(py)), + Value::Null => Ok(py.None()), + } +} + +fn parse_kwargs_qs(querystring: &str) -> (String, Vec) { + let re = regex::Regex::new(r"\$\(([^)]+)\)p").unwrap(); + + { + let kq_read = KWARGS_QUERYSTRINGS.read().unwrap(); + let qs = kq_read.get(querystring); + + if let Some(qs) = qs { + return qs.clone(); + } + }; + + let mut counter = 0; + let mut sequence = Vec::new(); + + let result = re.replace_all(querystring, |caps: ®ex::Captures| { + let account_id = caps[1].to_string(); + + sequence.push(account_id.clone()); + counter += 1; + + format!("${}", &counter) + }); + + let mut kq_write = KWARGS_QUERYSTRINGS.write().unwrap(); + kq_write.insert( + querystring.to_string(), + (result.clone().into(), sequence.clone()), + ); + (result.into(), sequence) +} + +fn composite_field_postgres_to_py<'a, T: FromSql<'a>>( + type_: &Type, + buf: &mut &'a [u8], + is_simple: bool, +) -> RustPSQLDriverPyResult { + if is_simple { + return T::from_sql_nullable(type_, Some(buf)).map_err(|err| { + RustPSQLDriverError::RustToPyValueConversionError(format!( + "Cannot convert PostgreSQL type {type_} into Python type, err: {err}", + )) + }); + } + postgres_types::private::read_value::(type_, buf).map_err(|err| { + RustPSQLDriverError::RustToPyValueConversionError(format!( + "Cannot convert PostgreSQL type {type_} into Python type, err: {err}", + )) + }) +} + +/// Convert rust array to python list. +/// +/// It can convert multidimensional arrays. +fn postgres_array_to_py( + py: Python<'_>, + array: Option>, +) -> Option> { + array.map(|array| { + inner_postgres_array_to_py( + py, + array.dimensions(), + array.iter().collect::>().as_slice(), + 0, + 0, + ) + }) +} + +/// Inner postgres array conversion to python list. +#[allow(clippy::cast_sign_loss)] +fn inner_postgres_array_to_py( + py: Python<'_>, + dimensions: &[Dimension], + data: &[T], + dimension_index: usize, + mut lower_bound: usize, +) -> Py +where + T: ToPyObject, +{ + let current_dimension = dimensions.get(dimension_index); + + if let Some(current_dimension) = current_dimension { + let possible_next_dimension = dimensions.get(dimension_index + 1); + match possible_next_dimension { + Some(next_dimension) => { + let final_list = PyList::empty_bound(py); + + for _ in 0..current_dimension.len as usize { + if dimensions.get(dimension_index + 1).is_some() { + let inner_pylist = inner_postgres_array_to_py( + py, + dimensions, + &data[lower_bound..next_dimension.len as usize + lower_bound], + dimension_index + 1, + 0, + ); + final_list.append(inner_pylist).unwrap(); + lower_bound += next_dimension.len as usize; + }; + } + + return final_list.unbind(); + } + None => { + return PyList::new_bound(py, data).unbind(); + } + } + } + + PyList::empty_bound(py).unbind() +} + +#[allow(clippy::too_many_lines)] +fn postgres_bytes_to_py( + py: Python<'_>, + type_: &Type, + buf: &mut &[u8], + is_simple: bool, +) -> RustPSQLDriverPyResult> { + match *type_ { + // ---------- Bytes Types ---------- + // Convert BYTEA type into Vector, then into PyBytes + Type::BYTEA => { + let vec_of_bytes = + composite_field_postgres_to_py::>>(type_, buf, is_simple)?; + if let Some(vec_of_bytes) = vec_of_bytes { + return Ok(PyBytes::new_bound(py, &vec_of_bytes).to_object(py)); + } + Ok(py.None()) + } + // // ---------- String Types ---------- + // // Convert TEXT and VARCHAR type into String, then into str + Type::TEXT | Type::VARCHAR | Type::XML => Ok(composite_field_postgres_to_py::< + Option, + >(type_, buf, is_simple)? + .to_object(py)), + // ---------- Boolean Types ---------- + // Convert BOOL type into bool + Type::BOOL => Ok( + composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py), + ), + // ---------- Number Types ---------- + // Convert SmallInt into i16, then into int + Type::INT2 => { + Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) + } + // Convert Integer into i32, then into int + Type::INT4 => { + Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) + } + // Convert BigInt into i64, then into int + Type::INT8 | Type::MONEY => { + Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) + } + // Convert REAL into f32, then into float + Type::FLOAT4 => { + Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) + } + // Convert DOUBLE PRECISION into f64, then into float + Type::FLOAT8 => { + Ok(composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py)) + } + // ---------- Date Types ---------- + // Convert DATE into NaiveDate, then into datetime.date + Type::DATE => Ok(composite_field_postgres_to_py::>( + type_, buf, is_simple, + )? + .to_object(py)), + // Convert Time into NaiveTime, then into datetime.time + Type::TIME => Ok(composite_field_postgres_to_py::>( + type_, buf, is_simple, + )? + .to_object(py)), + // Convert TIMESTAMP into NaiveDateTime, then into datetime.datetime + Type::TIMESTAMP => Ok(composite_field_postgres_to_py::>( + type_, buf, is_simple, + )? + .to_object(py)), + // Convert TIMESTAMP into NaiveDateTime, then into datetime.datetime + Type::TIMESTAMPTZ => Ok( + composite_field_postgres_to_py::>>(type_, buf, is_simple)? + .to_object(py), + ), + // ---------- UUID Types ---------- + // Convert UUID into Uuid type, then into String if possible + Type::UUID => { + let rust_uuid = composite_field_postgres_to_py::>(type_, buf, is_simple)?; + match rust_uuid { + Some(rust_uuid) => { + return Ok(PyString::new_bound(py, &rust_uuid.to_string()).to_object(py)) + } + None => Ok(py.None()), + } + } + // ---------- IpAddress Types ---------- + Type::INET => Ok( + composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py), + ), + // Convert JSON/JSONB into Serde Value, then into list or dict + Type::JSONB | Type::JSON => { + let db_json = composite_field_postgres_to_py::>(type_, buf, is_simple)?; + + match db_json { + Some(value) => Ok(build_python_from_serde_value(py, value)?), + None => Ok(py.None().to_object(py)), + } + } + // Convert MACADDR into inner type for macaddr6, then into str + Type::MACADDR => { + let macaddr_ = + composite_field_postgres_to_py::>(type_, buf, is_simple)?; + if let Some(macaddr_) = macaddr_ { + Ok(macaddr_.inner().to_string().to_object(py)) + } else { + Ok(py.None().to_object(py)) + } + } + Type::MACADDR8 => { + let macaddr_ = + composite_field_postgres_to_py::>(type_, buf, is_simple)?; + if let Some(macaddr_) = macaddr_ { + Ok(macaddr_.inner().to_string().to_object(py)) + } else { + Ok(py.None().to_object(py)) + } + } + Type::NUMERIC => { + if let Some(numeric_) = + composite_field_postgres_to_py::>(type_, buf, is_simple)? + { + return Ok(InnerDecimal(numeric_).to_object(py)); + } + Ok(py.None().to_object(py)) + } + // ---------- Geo Types ---------- + Type::POINT => { + let point_ = + composite_field_postgres_to_py::>(type_, buf, is_simple)?; + + match point_ { + Some(point_) => Ok(point_.into_py(py)), + None => Ok(py.None().to_object(py)), + } + } + Type::BOX => { + let box_ = composite_field_postgres_to_py::>(type_, buf, is_simple)?; + + match box_ { + Some(box_) => Ok(box_.into_py(py)), + None => Ok(py.None().to_object(py)), + } + } + Type::PATH => { + let path_ = + composite_field_postgres_to_py::>(type_, buf, is_simple)?; + + match path_ { + Some(path_) => Ok(path_.into_py(py)), + None => Ok(py.None().to_object(py)), + } + } + Type::LINE => { + let line_ = composite_field_postgres_to_py::>(type_, buf, is_simple)?; + + match line_ { + Some(line_) => Ok(line_.into_py(py)), + None => Ok(py.None().to_object(py)), + } + } + Type::LSEG => { + let lseg_ = + composite_field_postgres_to_py::>(type_, buf, is_simple)?; + + match lseg_ { + Some(lseg_) => Ok(lseg_.into_py(py)), + None => Ok(py.None().to_object(py)), + } + } + Type::CIRCLE => { + let circle_ = composite_field_postgres_to_py::>(type_, buf, is_simple)?; + + match circle_ { + Some(circle_) => Ok(circle_.into_py(py)), + None => Ok(py.None().to_object(py)), + } + } + Type::INTERVAL => { + let interval = + composite_field_postgres_to_py::>(type_, buf, is_simple)?; + if let Some(interval) = interval { + return Ok(InnerInterval(interval).to_object(py)); + } + Ok(py.None()) + } + // ---------- Array Text Types ---------- + Type::BOOL_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // Convert ARRAY of TEXT or VARCHAR into Vec, then into list[str] + Type::TEXT_ARRAY | Type::VARCHAR_ARRAY | Type::XML_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // ---------- Array Integer Types ---------- + // Convert ARRAY of SmallInt into Vec, then into list[int] + Type::INT2_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // Convert ARRAY of Integer into Vec, then into list[int] + Type::INT4_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // Convert ARRAY of BigInt into Vec, then into list[int] + Type::INT8_ARRAY | Type::MONEY_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // Convert ARRAY of Float4 into Vec, then into list[float] + Type::FLOAT4_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // Convert ARRAY of Float8 into Vec, then into list[float] + Type::FLOAT8_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // Convert ARRAY of Date into Vec, then into list[datetime.date] + Type::DATE_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // Convert ARRAY of Time into Vec, then into list[datetime.date] + Type::TIME_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // Convert ARRAY of TIMESTAMP into Vec, then into list[datetime.date] + Type::TIMESTAMP_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // Convert ARRAY of TIMESTAMPTZ into Vec>, then into list[datetime.date] + Type::TIMESTAMPTZ_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>>( + type_, buf, is_simple, + )?, + ) + .to_object(py)), + // Convert ARRAY of UUID into Vec>, then into list[UUID] + Type::UUID_ARRAY => { + let uuid_array = composite_field_postgres_to_py::>>( + type_, buf, is_simple, + )?; + Ok(postgres_array_to_py(py, uuid_array).to_object(py)) + } + // Convert ARRAY of INET into Vec, then into list[IPv4Address | IPv6Address] + Type::INET_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + Type::JSONB_ARRAY | Type::JSON_ARRAY => { + let db_json_array = composite_field_postgres_to_py::>>( + type_, buf, is_simple, + )?; + Ok(postgres_array_to_py(py, db_json_array).to_object(py)) + } + Type::NUMERIC_ARRAY => Ok(postgres_array_to_py( + py, + composite_field_postgres_to_py::>>(type_, buf, is_simple)?, + ) + .to_object(py)), + // ---------- Array Geo Types ---------- + Type::POINT_ARRAY => { + let point_array_ = + composite_field_postgres_to_py::>>(type_, buf, is_simple)?; + + Ok(postgres_array_to_py(py, point_array_).to_object(py)) + } + Type::BOX_ARRAY => { + let box_array_ = + composite_field_postgres_to_py::>>(type_, buf, is_simple)?; + + Ok(postgres_array_to_py(py, box_array_).to_object(py)) + } + Type::PATH_ARRAY => { + let path_array_ = composite_field_postgres_to_py::>>( + type_, buf, is_simple, + )?; + + Ok(postgres_array_to_py(py, path_array_).to_object(py)) + } + Type::LINE_ARRAY => { + let line_array_ = + composite_field_postgres_to_py::>>(type_, buf, is_simple)?; + + Ok(postgres_array_to_py(py, line_array_).to_object(py)) + } + Type::LSEG_ARRAY => { + let lseg_array_ = composite_field_postgres_to_py::>>( + type_, buf, is_simple, + )?; + + Ok(postgres_array_to_py(py, lseg_array_).to_object(py)) + } + Type::CIRCLE_ARRAY => { + let circle_array_ = + composite_field_postgres_to_py::>>(type_, buf, is_simple)?; + + Ok(postgres_array_to_py(py, circle_array_).to_object(py)) + } + Type::INTERVAL_ARRAY => { + let interval_array_ = composite_field_postgres_to_py::>>( + type_, buf, is_simple, + )?; + + Ok(postgres_array_to_py(py, interval_array_).to_object(py)) + } + _ => other_postgres_bytes_to_py(py, type_, buf, is_simple), + } +} + +/// Convert OTHER type to python. +/// +/// # Errors +/// May return result if type is unknown. +pub fn other_postgres_bytes_to_py( + py: Python<'_>, + type_: &Type, + buf: &mut &[u8], + is_simple: bool, +) -> RustPSQLDriverPyResult> { + if type_.name() == "vector" { + let vector = composite_field_postgres_to_py::>(type_, buf, is_simple)?; + match vector { + Some(real_vector) => { + return Ok(real_vector.to_vec().to_object(py)); + } + None => return Ok(py.None()), + } + } + + Err(RustPSQLDriverError::RustToPyValueConversionError( + format!("Cannot convert {type_} into Python type, please look at the custom_decoders functionality.") + )) +} + +/// Convert composite type from `PostgreSQL` to Python type. +/// +/// # Errors +/// May return error if there is any problem with bytes. +#[allow(clippy::cast_sign_loss)] +pub fn composite_postgres_to_py( + py: Python<'_>, + fields: &Vec, + buf: &mut &[u8], + custom_decoders: &Option>, +) -> RustPSQLDriverPyResult> { + let result_py_dict: Bound<'_, PyDict> = PyDict::new_bound(py); + + let num_fields = postgres_types::private::read_be_i32(buf).map_err(|err| { + RustPSQLDriverError::RustToPyValueConversionError(format!( + "Cannot read bytes data from PostgreSQL: {err}" + )) + })?; + if num_fields as usize != fields.len() { + return Err(RustPSQLDriverError::RustToPyValueConversionError(format!( + "invalid field count: {} vs {}", + num_fields, + fields.len() + ))); + } + + for field in fields { + let oid = postgres_types::private::read_be_i32(buf).map_err(|err| { + RustPSQLDriverError::RustToPyValueConversionError(format!( + "Cannot read bytes data from PostgreSQL: {err}" + )) + })? as u32; + + if oid != field.type_().oid() { + return Err(RustPSQLDriverError::RustToPyValueConversionError( + "unexpected OID".into(), + )); + } + + match field.type_().kind() { + Kind::Simple | Kind::Array(_) => { + result_py_dict.set_item( + field.name(), + postgres_bytes_to_py(py, field.type_(), buf, false)?.to_object(py), + )?; + } + Kind::Enum(_) => { + result_py_dict.set_item( + field.name(), + postgres_bytes_to_py(py, &Type::VARCHAR, buf, false)?.to_object(py), + )?; + } + _ => { + let (_, tail) = buf.split_at(4_usize); + *buf = tail; + result_py_dict.set_item( + field.name(), + raw_bytes_data_process(py, buf, field.name(), field.type_(), custom_decoders)? + .to_object(py), + )?; + } + } + } + + Ok(result_py_dict.to_object(py)) +} + +/// Process raw bytes from `PostgreSQL`. +/// +/// # Errors +/// +/// May return Err Result if cannot convert postgres +/// type into rust one. +pub fn raw_bytes_data_process( + py: Python<'_>, + raw_bytes_data: &mut &[u8], + column_name: &str, + column_type: &Type, + custom_decoders: &Option>, +) -> RustPSQLDriverPyResult> { + if let Some(custom_decoders) = custom_decoders { + let py_encoder_func = custom_decoders + .bind(py) + .get_item(column_name.to_lowercase()); + + if let Ok(Some(py_encoder_func)) = py_encoder_func { + return Ok(py_encoder_func + .call((raw_bytes_data.to_vec(),), None)? + .unbind()); + } + } + + match column_type.kind() { + Kind::Simple | Kind::Array(_) => { + postgres_bytes_to_py(py, column_type, raw_bytes_data, true) + } + Kind::Composite(fields) => { + composite_postgres_to_py(py, fields, raw_bytes_data, custom_decoders) + } + Kind::Enum(_) => postgres_bytes_to_py(py, &Type::VARCHAR, raw_bytes_data, true), + _ => Err(RustPSQLDriverError::RustToPyValueConversionError( + column_type.to_string(), + )), + } +} + +/// Convert type from postgres to python type. +/// +/// # Errors +/// +/// May return Err Result if cannot convert postgres +/// type into rust one. +pub fn postgres_to_py( + py: Python<'_>, + row: &Row, + column: &Column, + column_i: usize, + custom_decoders: &Option>, +) -> RustPSQLDriverPyResult> { + let raw_bytes_data = row.col_buffer(column_i); + if let Some(mut raw_bytes_data) = raw_bytes_data { + return raw_bytes_data_process( + py, + &mut raw_bytes_data, + column.name(), + column.type_(), + custom_decoders, + ); + } + Ok(py.None()) +} + +/// Convert Python sequence to Rust vector. +/// Also it checks that sequence has set/list/tuple type. +/// +/// # Errors +/// +/// May return error if cannot convert Python type into Rust one. +/// May return error if parameters type isn't correct. +fn py_sequence_to_rust(bind_parameters: &Bound) -> RustPSQLDriverPyResult>> { + let mut coord_values_sequence_vec: Vec> = vec![]; + + if bind_parameters.is_instance_of::() { + let bind_pyset_parameters = bind_parameters.downcast::().unwrap(); + + for one_parameter in bind_pyset_parameters { + let extracted_parameter = one_parameter.extract::>().map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError( + format!("Error on sequence type extraction, please use correct list/tuple/set, {bind_parameters}") + ) + })?; + coord_values_sequence_vec.push(extracted_parameter); + } + } else if bind_parameters.is_instance_of::() + | bind_parameters.is_instance_of::() + { + coord_values_sequence_vec = bind_parameters.extract::>>().map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError( + format!("Error on sequence type extraction, please use correct list/tuple/set, {bind_parameters}") + ) + })?; + } else { + return Err(RustPSQLDriverError::PyToRustValueConversionError(format!( + "Invalid sequence type, please use list/tuple/set, {bind_parameters}" + ))); + }; + + Ok::>, RustPSQLDriverError>(coord_values_sequence_vec) +} diff --git a/src/value_converter/mod.rs b/src/value_converter/mod.rs new file mode 100644 index 00000000..e8cbc82b --- /dev/null +++ b/src/value_converter/mod.rs @@ -0,0 +1,5 @@ +pub mod additional_types; +pub mod consts; +pub mod funcs; +pub mod models; +pub mod utils; diff --git a/src/value_converter/models/decimal.rs b/src/value_converter/models/decimal.rs new file mode 100644 index 00000000..13d009cc --- /dev/null +++ b/src/value_converter/models/decimal.rs @@ -0,0 +1,30 @@ +use postgres_types::{FromSql, Type}; +use pyo3::{types::PyAnyMethods, PyObject, Python, ToPyObject}; +use rust_decimal::Decimal; + +use crate::value_converter::consts::get_decimal_cls; + +pub struct InnerDecimal(pub Decimal); + +impl ToPyObject for InnerDecimal { + fn to_object(&self, py: Python<'_>) -> PyObject { + let dec_cls = get_decimal_cls(py).expect("failed to load decimal.Decimal"); + let ret = dec_cls + .call1((self.0.to_string(),)) + .expect("failed to call decimal.Decimal(value)"); + ret.to_object(py) + } +} + +impl<'a> FromSql<'a> for InnerDecimal { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + Ok(InnerDecimal(::from_sql(ty, raw)?)) + } + + fn accepts(_ty: &Type) -> bool { + true + } +} diff --git a/src/value_converter/models/dto.rs b/src/value_converter/models/dto.rs new file mode 100644 index 00000000..8609a600 --- /dev/null +++ b/src/value_converter/models/dto.rs @@ -0,0 +1,491 @@ +use chrono::{self, DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; +use geo_types::{Line as LineSegment, LineString, Point, Rect}; +use macaddr::{MacAddr6, MacAddr8}; +use pg_interval::Interval; +use postgres_types::ToSql; +use rust_decimal::Decimal; +use serde_json::{json, Value}; +use std::{fmt::Debug, net::IpAddr}; +use uuid::Uuid; + +use bytes::{BufMut, BytesMut}; +use postgres_protocol::types; +use pyo3::{PyObject, Python, ToPyObject}; +use tokio_postgres::types::{to_sql_checked, Type}; + +use crate::{ + exceptions::rust_errors::{RustPSQLDriverError, RustPSQLDriverPyResult}, + value_converter::additional_types::{ + Circle, Line, RustLineSegment, RustLineString, RustPoint, RustRect, + }, +}; +use pgvector::Vector as PgVector; +use postgres_array::{array::Array, Dimension}; + +#[derive(Debug, Clone, PartialEq)] +pub enum PythonDTO { + // Primitive + PyNone, + PyBytes(Vec), + PyBool(bool), + PyUUID(Uuid), + PyVarChar(String), + PyText(String), + PyString(String), + PyIntI16(i16), + PyIntI32(i32), + PyIntI64(i64), + PyIntU32(u32), + PyIntU64(u64), + PyFloat32(f32), + PyFloat64(f64), + PyMoney(i64), + PyDate(NaiveDate), + PyTime(NaiveTime), + PyDateTime(NaiveDateTime), + PyDateTimeTz(DateTime), + PyInterval(Interval), + PyIpAddress(IpAddr), + PyList(Vec), + PyArray(Array), + PyTuple(Vec), + PyJsonb(Value), + PyJson(Value), + PyMacAddr6(MacAddr6), + PyMacAddr8(MacAddr8), + PyDecimal(Decimal), + PyCustomType(Vec), + PyPoint(Point), + PyBox(Rect), + PyPath(LineString), + PyLine(Line), + PyLineSegment(LineSegment), + PyCircle(Circle), + // Arrays + PyBoolArray(Array), + PyUuidArray(Array), + PyVarCharArray(Array), + PyTextArray(Array), + PyInt16Array(Array), + PyInt32Array(Array), + PyInt64Array(Array), + PyFloat32Array(Array), + PyFloat64Array(Array), + PyMoneyArray(Array), + PyIpAddressArray(Array), + PyJSONBArray(Array), + PyJSONArray(Array), + PyDateArray(Array), + PyTimeArray(Array), + PyDateTimeArray(Array), + PyDateTimeTZArray(Array), + PyMacAddr6Array(Array), + PyMacAddr8Array(Array), + PyNumericArray(Array), + PyPointArray(Array), + PyBoxArray(Array), + PyPathArray(Array), + PyLineArray(Array), + PyLsegArray(Array), + PyCircleArray(Array), + PyIntervalArray(Array), + // PgVector + PyPgVector(Vec), +} + +impl ToPyObject for PythonDTO { + fn to_object(&self, py: Python<'_>) -> PyObject { + match self { + PythonDTO::PyNone => py.None(), + PythonDTO::PyBool(pybool) => pybool.to_object(py), + PythonDTO::PyString(py_string) + | PythonDTO::PyText(py_string) + | PythonDTO::PyVarChar(py_string) => py_string.to_object(py), + PythonDTO::PyIntI32(pyint) => pyint.to_object(py), + PythonDTO::PyIntI64(pyint) => pyint.to_object(py), + PythonDTO::PyIntU64(pyint) => pyint.to_object(py), + PythonDTO::PyFloat32(pyfloat) => pyfloat.to_object(py), + PythonDTO::PyFloat64(pyfloat) => pyfloat.to_object(py), + _ => unreachable!(), + } + } +} + +impl PythonDTO { + /// Return type of the Array for `PostgreSQL`. + /// + /// Since every Array must have concrete type, + /// we must say exactly what type of array we try to pass into + /// postgres. + /// + /// # Errors + /// May return Err Result if there is no support for passed python type. + pub fn array_type(&self) -> RustPSQLDriverPyResult { + match self { + PythonDTO::PyBool(_) => Ok(tokio_postgres::types::Type::BOOL_ARRAY), + PythonDTO::PyUUID(_) => Ok(tokio_postgres::types::Type::UUID_ARRAY), + PythonDTO::PyVarChar(_) | PythonDTO::PyString(_) => { + Ok(tokio_postgres::types::Type::VARCHAR_ARRAY) + } + PythonDTO::PyText(_) => Ok(tokio_postgres::types::Type::TEXT_ARRAY), + PythonDTO::PyIntI16(_) => Ok(tokio_postgres::types::Type::INT2_ARRAY), + PythonDTO::PyIntI32(_) | PythonDTO::PyIntU32(_) => { + Ok(tokio_postgres::types::Type::INT4_ARRAY) + } + PythonDTO::PyIntI64(_) => Ok(tokio_postgres::types::Type::INT8_ARRAY), + PythonDTO::PyFloat32(_) => Ok(tokio_postgres::types::Type::FLOAT4_ARRAY), + PythonDTO::PyFloat64(_) => Ok(tokio_postgres::types::Type::FLOAT8_ARRAY), + PythonDTO::PyMoney(_) => Ok(tokio_postgres::types::Type::MONEY_ARRAY), + PythonDTO::PyIpAddress(_) => Ok(tokio_postgres::types::Type::INET_ARRAY), + PythonDTO::PyJsonb(_) => Ok(tokio_postgres::types::Type::JSONB_ARRAY), + PythonDTO::PyJson(_) => Ok(tokio_postgres::types::Type::JSON_ARRAY), + PythonDTO::PyDate(_) => Ok(tokio_postgres::types::Type::DATE_ARRAY), + PythonDTO::PyTime(_) => Ok(tokio_postgres::types::Type::TIME_ARRAY), + PythonDTO::PyDateTime(_) => Ok(tokio_postgres::types::Type::TIMESTAMP_ARRAY), + PythonDTO::PyDateTimeTz(_) => Ok(tokio_postgres::types::Type::TIMESTAMPTZ_ARRAY), + PythonDTO::PyMacAddr6(_) => Ok(tokio_postgres::types::Type::MACADDR_ARRAY), + PythonDTO::PyMacAddr8(_) => Ok(tokio_postgres::types::Type::MACADDR8_ARRAY), + PythonDTO::PyDecimal(_) => Ok(tokio_postgres::types::Type::NUMERIC_ARRAY), + PythonDTO::PyPoint(_) => Ok(tokio_postgres::types::Type::POINT_ARRAY), + PythonDTO::PyBox(_) => Ok(tokio_postgres::types::Type::BOX_ARRAY), + PythonDTO::PyPath(_) => Ok(tokio_postgres::types::Type::PATH_ARRAY), + PythonDTO::PyLine(_) => Ok(tokio_postgres::types::Type::LINE_ARRAY), + PythonDTO::PyLineSegment(_) => Ok(tokio_postgres::types::Type::LSEG_ARRAY), + PythonDTO::PyCircle(_) => Ok(tokio_postgres::types::Type::CIRCLE_ARRAY), + PythonDTO::PyInterval(_) => Ok(tokio_postgres::types::Type::INTERVAL_ARRAY), + _ => Err(RustPSQLDriverError::PyToRustValueConversionError( + "Can't process array type, your type doesn't have support yet".into(), + )), + } + } + + /// Convert enum into serde `Value`. + /// + /// # Errors + /// May return Err Result if cannot convert python type into rust. + pub fn to_serde_value(&self) -> RustPSQLDriverPyResult { + match self { + PythonDTO::PyNone => Ok(Value::Null), + PythonDTO::PyBool(pybool) => Ok(json!(pybool)), + PythonDTO::PyString(pystring) + | PythonDTO::PyText(pystring) + | PythonDTO::PyVarChar(pystring) => Ok(json!(pystring)), + PythonDTO::PyIntI32(pyint) => Ok(json!(pyint)), + PythonDTO::PyIntI64(pyint) => Ok(json!(pyint)), + PythonDTO::PyIntU64(pyint) => Ok(json!(pyint)), + PythonDTO::PyFloat32(pyfloat) => Ok(json!(pyfloat)), + PythonDTO::PyFloat64(pyfloat) => Ok(json!(pyfloat)), + PythonDTO::PyList(pylist) => { + let mut vec_serde_values: Vec = vec![]; + + for py_object in pylist { + vec_serde_values.push(py_object.to_serde_value()?); + } + + Ok(json!(vec_serde_values)) + } + PythonDTO::PyArray(array) => Ok(json!(pythondto_array_to_serde(Some(array.clone()))?)), + PythonDTO::PyJsonb(py_dict) | PythonDTO::PyJson(py_dict) => Ok(py_dict.clone()), + _ => Err(RustPSQLDriverError::PyToRustValueConversionError( + "Cannot convert your type into Rust type".into(), + )), + } + } +} + +/// Implement `ToSql` trait. +/// +/// It allows us to pass `PythonDTO` enum as parameter +/// directly into `.execute()` method in +/// `DatabasePool`, `Connection` and `Transaction`. +impl ToSql for PythonDTO { + /// Answer the question Is this type can be passed into sql? + /// + /// Always True. + fn accepts(_ty: &tokio_postgres::types::Type) -> bool + where + Self: Sized, + { + true + } + + /// Convert our `PythonDTO` enum into bytes. + /// + /// We convert every inner type of `PythonDTO` enum variant + /// into bytes and write them into bytes buffer. + /// + /// # Errors + /// + /// May return Err Result if cannot write bytes into buffer. + #[allow(clippy::too_many_lines)] + fn to_sql( + &self, + ty: &tokio_postgres::types::Type, + out: &mut BytesMut, + ) -> Result> + where + Self: Sized, + { + let mut return_is_null_true: bool = false; + if *self == PythonDTO::PyNone { + return_is_null_true = true; + } + + match self { + PythonDTO::PyNone => {} + PythonDTO::PyCustomType(some_bytes) => { + <&[u8] as ToSql>::to_sql(&some_bytes.as_slice(), ty, out)?; + } + PythonDTO::PyBytes(pybytes) => { + as ToSql>::to_sql(pybytes, ty, out)?; + } + PythonDTO::PyBool(boolean) => types::bool_to_sql(*boolean, out), + PythonDTO::PyVarChar(string) => { + <&str as ToSql>::to_sql(&string.as_str(), ty, out)?; + } + PythonDTO::PyText(string) => { + <&str as ToSql>::to_sql(&string.as_str(), ty, out)?; + } + PythonDTO::PyUUID(pyuuid) => { + ::to_sql(pyuuid, ty, out)?; + } + PythonDTO::PyString(string) => { + <&str as ToSql>::to_sql(&string.as_str(), ty, out)?; + } + PythonDTO::PyIntI16(int) => out.put_i16(*int), + PythonDTO::PyIntI32(int) => out.put_i32(*int), + PythonDTO::PyIntI64(int) | PythonDTO::PyMoney(int) => out.put_i64(*int), + PythonDTO::PyIntU32(int) => out.put_u32(*int), + PythonDTO::PyIntU64(int) => out.put_u64(*int), + PythonDTO::PyFloat32(float) => out.put_f32(*float), + PythonDTO::PyFloat64(float) => out.put_f64(*float), + PythonDTO::PyDate(pydate) => { + <&NaiveDate as ToSql>::to_sql(&pydate, ty, out)?; + } + PythonDTO::PyTime(pytime) => { + <&NaiveTime as ToSql>::to_sql(&pytime, ty, out)?; + } + PythonDTO::PyDateTime(pydatetime_no_tz) => { + <&NaiveDateTime as ToSql>::to_sql(&pydatetime_no_tz, ty, out)?; + } + PythonDTO::PyDateTimeTz(pydatetime_tz) => { + <&DateTime as ToSql>::to_sql(&pydatetime_tz, ty, out)?; + } + PythonDTO::PyInterval(pyinterval) => { + <&Interval as ToSql>::to_sql(&pyinterval, ty, out)?; + } + PythonDTO::PyIpAddress(pyidaddress) => { + <&IpAddr as ToSql>::to_sql(&pyidaddress, ty, out)?; + } + PythonDTO::PyMacAddr6(pymacaddr) => { + <&[u8] as ToSql>::to_sql(&pymacaddr.as_bytes(), ty, out)?; + } + PythonDTO::PyMacAddr8(pymacaddr) => { + <&[u8] as ToSql>::to_sql(&pymacaddr.as_bytes(), ty, out)?; + } + PythonDTO::PyPoint(pypoint) => { + <&RustPoint as ToSql>::to_sql(&&RustPoint::new(*pypoint), ty, out)?; + } + PythonDTO::PyBox(pybox) => { + <&RustRect as ToSql>::to_sql(&&RustRect::new(*pybox), ty, out)?; + } + PythonDTO::PyPath(pypath) => { + <&RustLineString as ToSql>::to_sql(&&RustLineString::new(pypath.clone()), ty, out)?; + } + PythonDTO::PyLine(pyline) => { + <&Line as ToSql>::to_sql(&pyline, ty, out)?; + } + PythonDTO::PyLineSegment(pylinesegment) => { + <&RustLineSegment as ToSql>::to_sql( + &&RustLineSegment::new(*pylinesegment), + ty, + out, + )?; + } + PythonDTO::PyCircle(pycircle) => { + <&Circle as ToSql>::to_sql(&pycircle, ty, out)?; + } + PythonDTO::PyList(py_iterable) | PythonDTO::PyTuple(py_iterable) => { + let mut items = Vec::new(); + for inner in py_iterable { + items.push(inner); + } + if items.is_empty() { + return_is_null_true = true; + } else { + items.to_sql(&items[0].array_type()?, out)?; + } + } + PythonDTO::PyArray(array) => { + if let Some(first_elem) = array.iter().nth(0) { + match first_elem.array_type() { + Ok(ok_type) => { + array.to_sql(&ok_type, out)?; + } + Err(_) => { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "Cannot define array type.".into(), + ))? + } + } + } + } + PythonDTO::PyJsonb(py_dict) | PythonDTO::PyJson(py_dict) => { + <&Value as ToSql>::to_sql(&py_dict, ty, out)?; + } + PythonDTO::PyDecimal(py_decimal) => { + ::to_sql(py_decimal, ty, out)?; + } + PythonDTO::PyBoolArray(array) => { + array.to_sql(&Type::BOOL_ARRAY, out)?; + } + PythonDTO::PyUuidArray(array) => { + array.to_sql(&Type::UUID_ARRAY, out)?; + } + PythonDTO::PyVarCharArray(array) => { + array.to_sql(&Type::VARCHAR_ARRAY, out)?; + } + PythonDTO::PyTextArray(array) => { + array.to_sql(&Type::TEXT_ARRAY, out)?; + } + PythonDTO::PyInt16Array(array) => { + array.to_sql(&Type::INT2_ARRAY, out)?; + } + PythonDTO::PyInt32Array(array) => { + array.to_sql(&Type::INT4_ARRAY, out)?; + } + PythonDTO::PyInt64Array(array) => { + array.to_sql(&Type::INT8_ARRAY, out)?; + } + PythonDTO::PyFloat32Array(array) => { + array.to_sql(&Type::FLOAT4, out)?; + } + PythonDTO::PyFloat64Array(array) => { + array.to_sql(&Type::FLOAT8_ARRAY, out)?; + } + PythonDTO::PyMoneyArray(array) => { + array.to_sql(&Type::MONEY_ARRAY, out)?; + } + PythonDTO::PyIpAddressArray(array) => { + array.to_sql(&Type::INET_ARRAY, out)?; + } + PythonDTO::PyJSONBArray(array) => { + array.to_sql(&Type::JSONB_ARRAY, out)?; + } + PythonDTO::PyJSONArray(array) => { + array.to_sql(&Type::JSON_ARRAY, out)?; + } + PythonDTO::PyDateArray(array) => { + array.to_sql(&Type::DATE_ARRAY, out)?; + } + PythonDTO::PyTimeArray(array) => { + array.to_sql(&Type::TIME_ARRAY, out)?; + } + PythonDTO::PyDateTimeArray(array) => { + array.to_sql(&Type::TIMESTAMP_ARRAY, out)?; + } + PythonDTO::PyDateTimeTZArray(array) => { + array.to_sql(&Type::TIMESTAMPTZ_ARRAY, out)?; + } + PythonDTO::PyMacAddr6Array(array) => { + array.to_sql(&Type::MACADDR_ARRAY, out)?; + } + PythonDTO::PyMacAddr8Array(array) => { + array.to_sql(&Type::MACADDR8_ARRAY, out)?; + } + PythonDTO::PyNumericArray(array) => { + array.to_sql(&Type::NUMERIC_ARRAY, out)?; + } + PythonDTO::PyPointArray(array) => { + array.to_sql(&Type::POINT_ARRAY, out)?; + } + PythonDTO::PyBoxArray(array) => { + array.to_sql(&Type::BOX_ARRAY, out)?; + } + PythonDTO::PyPathArray(array) => { + array.to_sql(&Type::PATH_ARRAY, out)?; + } + PythonDTO::PyLineArray(array) => { + array.to_sql(&Type::LINE_ARRAY, out)?; + } + PythonDTO::PyLsegArray(array) => { + array.to_sql(&Type::LSEG_ARRAY, out)?; + } + PythonDTO::PyCircleArray(array) => { + array.to_sql(&Type::CIRCLE_ARRAY, out)?; + } + PythonDTO::PyIntervalArray(array) => { + array.to_sql(&Type::INTERVAL_ARRAY, out)?; + } + PythonDTO::PyPgVector(vector) => { + ::to_sql(&PgVector::from(vector.clone()), ty, out)?; + } + } + + if return_is_null_true { + Ok(tokio_postgres::types::IsNull::Yes) + } else { + Ok(tokio_postgres::types::IsNull::No) + } + } + + to_sql_checked!(); +} + +/// Convert Array of `PythonDTO`s to serde `Value`. +/// +/// It can convert multidimensional arrays. +fn pythondto_array_to_serde(array: Option>) -> RustPSQLDriverPyResult { + match array { + Some(array) => inner_pythondto_array_to_serde( + array.dimensions(), + array.iter().collect::>().as_slice(), + 0, + 0, + ), + None => Ok(Value::Null), + } +} + +/// Inner conversion array of `PythonDTO`s to serde `Value`. +#[allow(clippy::cast_sign_loss)] +fn inner_pythondto_array_to_serde( + dimensions: &[Dimension], + data: &[&PythonDTO], + dimension_index: usize, + mut lower_bound: usize, +) -> RustPSQLDriverPyResult { + let current_dimension = dimensions.get(dimension_index); + + if let Some(current_dimension) = current_dimension { + let possible_next_dimension = dimensions.get(dimension_index + 1); + match possible_next_dimension { + Some(next_dimension) => { + let mut final_list: Value = Value::Array(vec![]); + + for _ in 0..current_dimension.len as usize { + if dimensions.get(dimension_index + 1).is_some() { + let inner_pylist = inner_pythondto_array_to_serde( + dimensions, + &data[lower_bound..next_dimension.len as usize + lower_bound], + dimension_index + 1, + 0, + )?; + match final_list { + Value::Array(ref mut array) => array.push(inner_pylist), + _ => unreachable!(), + } + lower_bound += next_dimension.len as usize; + }; + } + + return Ok(final_list); + } + None => { + return data.iter().map(|x| x.to_serde_value()).collect(); + } + } + } + + Ok(Value::Array(vec![])) +} diff --git a/src/value_converter/models/interval.rs b/src/value_converter/models/interval.rs new file mode 100644 index 00000000..7259e20d --- /dev/null +++ b/src/value_converter/models/interval.rs @@ -0,0 +1,37 @@ +use pg_interval::Interval; +use postgres_types::{FromSql, Type}; +use pyo3::{ + types::{PyAnyMethods, PyDict, PyDictMethods}, + PyObject, Python, ToPyObject, +}; + +use crate::value_converter::consts::get_timedelta_cls; + +pub struct InnerInterval(pub Interval); + +impl ToPyObject for InnerInterval { + fn to_object(&self, py: Python<'_>) -> PyObject { + let td_cls = get_timedelta_cls(py).expect("failed to load datetime.timedelta"); + let pydict = PyDict::new_bound(py); + let months = self.0.months * 30; + let _ = pydict.set_item("days", self.0.days + months); + let _ = pydict.set_item("microseconds", self.0.microseconds); + let ret = td_cls + .call((), Some(&pydict)) + .expect("failed to call datetime.timedelta(days=<>, microseconds=<>)"); + ret.to_object(py) + } +} + +impl<'a> FromSql<'a> for InnerInterval { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + Ok(InnerInterval(::from_sql(ty, raw)?)) + } + + fn accepts(_ty: &Type) -> bool { + true + } +} diff --git a/src/value_converter/models/mod.rs b/src/value_converter/models/mod.rs new file mode 100644 index 00000000..92d26e49 --- /dev/null +++ b/src/value_converter/models/mod.rs @@ -0,0 +1,5 @@ +pub mod decimal; +pub mod dto; +pub mod interval; +pub mod serde_value; +pub mod uuid; diff --git a/src/value_converter/models/serde_value.rs b/src/value_converter/models/serde_value.rs new file mode 100644 index 00000000..b39f7737 --- /dev/null +++ b/src/value_converter/models/serde_value.rs @@ -0,0 +1,89 @@ +use postgres_types::FromSql; +use serde_json::{json, Value}; +use uuid::Uuid; + +use pyo3::{ + types::{PyAnyMethods, PyDict, PyList}, + Bound, FromPyObject, Py, PyAny, PyObject, PyResult, Python, ToPyObject, +}; +use tokio_postgres::types::Type; + +use crate::{ + exceptions::rust_errors::{RustPSQLDriverError, RustPSQLDriverPyResult}, + value_converter::funcs::{from_python::py_to_rust, to_python::build_python_from_serde_value}, +}; + +/// Struct for Value. +/// +/// We use custom struct because we need to implement external traits +/// to it. +#[derive(Clone)] +pub struct InternalSerdeValue(Value); + +impl<'a> FromPyObject<'a> for InternalSerdeValue { + fn extract_bound(ob: &Bound<'a, PyAny>) -> PyResult { + let serde_value = build_serde_value(ob.clone().unbind())?; + + Ok(InternalSerdeValue(serde_value)) + } +} + +impl ToPyObject for InternalSerdeValue { + fn to_object(&self, py: Python<'_>) -> PyObject { + match build_python_from_serde_value(py, self.0.clone()) { + Ok(ok_value) => ok_value, + Err(_) => py.None(), + } + } +} + +impl<'a> FromSql<'a> for InternalSerdeValue { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + Ok(InternalSerdeValue(::from_sql(ty, raw)?)) + } + + fn accepts(_ty: &Type) -> bool { + true + } +} + +/// Convert python List of Dict type or just Dict into serde `Value`. +/// +/// # Errors +/// May return error if cannot convert Python type into Rust one. +#[allow(clippy::needless_pass_by_value)] +pub fn build_serde_value(value: Py) -> RustPSQLDriverPyResult { + Python::with_gil(|gil| { + let bind_value = value.bind(gil); + if bind_value.is_instance_of::() { + let mut result_vec: Vec = vec![]; + + let params = bind_value.extract::>>()?; + + for inner in params { + let inner_bind = inner.bind(gil); + if inner_bind.is_instance_of::() { + let python_dto = py_to_rust(inner_bind)?; + result_vec.push(python_dto.to_serde_value()?); + } else if inner_bind.is_instance_of::() { + let serde_value = build_serde_value(inner)?; + result_vec.push(serde_value); + } else { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "PyJSON must have dicts.".to_string(), + )); + } + } + Ok(json!(result_vec)) + } else if bind_value.is_instance_of::() { + return py_to_rust(bind_value)?.to_serde_value(); + } else { + return Err(RustPSQLDriverError::PyToRustValueConversionError( + "PyJSON must be dict value.".to_string(), + )); + } + }) +} diff --git a/src/value_converter/models/uuid.rs b/src/value_converter/models/uuid.rs new file mode 100644 index 00000000..100bfbf8 --- /dev/null +++ b/src/value_converter/models/uuid.rs @@ -0,0 +1,46 @@ +use postgres_types::FromSql; +use uuid::Uuid; + +use pyo3::{ + types::PyAnyMethods, Bound, FromPyObject, PyAny, PyObject, PyResult, Python, ToPyObject, +}; +use tokio_postgres::types::Type; + +use crate::exceptions::rust_errors::RustPSQLDriverError; + +/// Struct for Uuid. +/// +/// We use custom struct because we need to implement external traits +/// to it. +#[derive(Clone, Copy)] +pub struct InternalUuid(Uuid); + +impl<'a> FromPyObject<'a> for InternalUuid { + fn extract_bound(obj: &Bound<'a, PyAny>) -> PyResult { + let uuid_value = Uuid::parse_str(obj.str()?.extract::<&str>()?).map_err(|_| { + RustPSQLDriverError::PyToRustValueConversionError( + "Cannot convert UUID Array to inner rust type, check you parameters.".into(), + ) + })?; + Ok(InternalUuid(uuid_value)) + } +} + +impl ToPyObject for InternalUuid { + fn to_object(&self, py: Python<'_>) -> PyObject { + self.0.to_string().as_str().to_object(py) + } +} + +impl<'a> FromSql<'a> for InternalUuid { + fn from_sql( + ty: &Type, + raw: &'a [u8], + ) -> Result> { + Ok(InternalUuid(::from_sql(ty, raw)?)) + } + + fn accepts(_ty: &Type) -> bool { + true + } +} diff --git a/src/value_converter/utils.rs b/src/value_converter/utils.rs new file mode 100644 index 00000000..c94b2669 --- /dev/null +++ b/src/value_converter/utils.rs @@ -0,0 +1,25 @@ +use pyo3::{types::PyAnyMethods, FromPyObject, PyAny}; + +use crate::exceptions::rust_errors::RustPSQLDriverError; + +/// Extract a value from a Python object, raising an error if missing or invalid +/// +/// # Errors +/// This function will return `Err` in the following cases: +/// - The Python object does not have the specified attribute +/// - The attribute exists but cannot be extracted into the specified Rust type +pub fn extract_value_from_python_object_or_raise<'py, T>( + parameter: &'py pyo3::Bound<'_, PyAny>, + attr_name: &str, +) -> Result +where + T: FromPyObject<'py>, +{ + parameter + .getattr(attr_name) + .ok() + .and_then(|attr| attr.extract::().ok()) + .ok_or_else(|| { + RustPSQLDriverError::PyToRustValueConversionError("Invalid attribute".into()) + }) +}