From 0561f5c456f1229975bb2b785b86e09fa271814d Mon Sep 17 00:00:00 2001 From: "chandr-andr (Kiselev Aleksandr)" Date: Wed, 12 Jun 2024 23:15:42 +0200 Subject: [PATCH 1/3] Supported more types Signed-off-by: chandr-andr (Kiselev Aleksandr) --- Cargo.lock | 340 +++++++++++++++++++++++++++++++++++++++-- Cargo.toml | 10 +- src/value_converter.rs | 45 +++++- 3 files changed, 379 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f6d18d9..d681586d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,17 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "ahash" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891477e0c6a8957309ee5c45a6368af3ae14bb510732d2684ffa19af310920f9" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -47,6 +58,12 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d62b7694a562cdf5a74227903507c56ab2cc8bdd1f781ed5cb4cf9c9f810bfc" +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "async-trait" version = "0.1.79" @@ -55,7 +72,7 @@ checksum = "a507401cad91ec6a857ed5513a2073c82a9b9048762b885bb98655b306964681" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -91,6 +108,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -100,12 +129,58 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6362ed55def622cddc70a4746a68554d7b687713770de539e59a739b249f8ed" +dependencies = [ + "borsh-derive", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ef8005764f53cd4dca619f5bf64cafd4664dada50ece25e4d81de54c80cc0b" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.58", + "syn_derive", +] + [[package]] name = "bumpalo" version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +[[package]] +name = "bytecheck" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23cdc57ce23ac53c931e88a43d06d070a6fd142f2617be5855eb75efc9beb1c2" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db406d29fbcd95542e92559bed4d8ad92636d1ca8b3b72ede10b4bcc010e659" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.5.0" @@ -130,6 +205,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + [[package]] name = "chrono" version = "0.4.37" @@ -231,6 +312,12 @@ dependencies = [ "subtle", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "fallible-iterator" version = "0.2.0" @@ -243,6 +330,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fcfdc7a0362c9f4444381a9e697c79d435fe65b52a37466fc2c1184cee9edc6" +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures" version = "0.3.30" @@ -299,7 +392,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -359,6 +452,21 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.4.1" @@ -409,6 +517,16 @@ dependencies = [ "cc", ] +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + [[package]] name = "indoc" version = "2.0.5" @@ -639,7 +757,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -681,6 +799,38 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro2" version = "1.0.79" @@ -705,6 +855,7 @@ dependencies = [ "postgres-types", "pyo3", "pyo3-asyncio", + "rust_decimal 1.35.0 (git+https://github.com/chandr-andr/rust-decimal.git?branch=psqlpy)", "serde_json", "thiserror", "tokio", @@ -712,6 +863,26 @@ dependencies = [ "uuid", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "pyo3" version = "0.21.1" @@ -728,6 +899,7 @@ dependencies = [ "pyo3-build-config", "pyo3-ffi", "pyo3-macros", + "rust_decimal 1.35.0 (registry+https://github.com/rust-lang/crates.io-index)", "unindent", ] @@ -772,7 +944,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -785,7 +957,7 @@ dependencies = [ "proc-macro2", "pyo3-build-config", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -797,6 +969,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -865,6 +1043,70 @@ version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +[[package]] +name = "rend" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71fe3824f5629716b1589be05dacd749f6aa084c87e00e016714a8cdfccc997c" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cba464629b3394fc4dbc6f940ff8f5b4ff5c7aef40f29166fd4ad12acbc99c0" +dependencies = [ + "bitvec", + "bytecheck", + "bytes", + "hashbrown 0.12.3", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dddfff8de25e6f62b9d64e6e432bf1c6736c57d20323e15ee10435fbda7c65" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +dependencies = [ + "arrayvec", + "num-traits", +] + +[[package]] +name = "rust_decimal" +version = "1.35.0" +source = "git+https://github.com/chandr-andr/rust-decimal.git?branch=psqlpy#5b5fbdd57502c3d7990759f03123e063f4c0c28d" +dependencies = [ + "arrayvec", + "borsh", + "bytes", + "num-traits", + "postgres-types", + "rand", + "rkyv", + "serde", + "serde_json", +] + [[package]] name = "rustc-demangle" version = "0.1.23" @@ -883,6 +1125,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "serde" version = "1.0.197" @@ -900,7 +1148,7 @@ checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -934,6 +1182,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "siphasher" version = "0.3.11" @@ -982,6 +1236,17 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.58" @@ -993,6 +1258,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.14" @@ -1016,7 +1299,7 @@ checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -1061,7 +1344,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -1103,6 +1386,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + [[package]] name = "tracing" version = "0.1.40" @@ -1122,7 +1422,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", ] [[package]] @@ -1215,7 +1515,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.58", "wasm-bindgen-shared", ] @@ -1237,7 +1537,7 @@ checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.58", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1409,3 +1709,21 @@ name = "windows_x86_64_msvc" version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] diff --git a/Cargo.toml b/Cargo.toml index 15167f55..c75d9d57 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,11 @@ crate-type = ["cdylib"] [dependencies] deadpool-postgres = { git = "https://github.com/chandr-andr/deadpool.git", branch = "master" } -pyo3 = { version = "*", features = ["chrono", "experimental-async"] } +pyo3 = { version = "*", features = [ + "chrono", + "experimental-async", + "rust_decimal", +] } pyo3-asyncio = { git = "https://github.com/chandr-andr/pyo3-asyncio.git", version = "0.20.0", features = [ "tokio-runtime", ] } @@ -34,3 +38,7 @@ postgres-types = { git = "https://github.com/chandr-andr/rust-postgres.git", bra "derive", ] } postgres-protocol = { git = "https://github.com/chandr-andr/rust-postgres.git", branch = "master" } +rust_decimal = { git = "https://github.com/chandr-andr/rust-decimal.git", branch = "psqlpy", features = [ + "db-postgres", + "db-tokio-postgres", +] } diff --git a/src/value_converter.rs b/src/value_converter.rs index d2cf3b66..eafd7b1f 100644 --- a/src/value_converter.rs +++ b/src/value_converter.rs @@ -1,6 +1,7 @@ use chrono::{self, DateTime, FixedOffset, NaiveDate, NaiveDateTime, NaiveTime}; use macaddr::{MacAddr6, MacAddr8}; use postgres_types::{Field, FromSql, Kind, ToSql}; +use rust_decimal::Decimal; use serde_json::{json, Map, Value}; use std::{fmt::Debug, net::IpAddr}; use uuid::Uuid; @@ -8,11 +9,12 @@ use uuid::Uuid; use bytes::{BufMut, BytesMut}; use postgres_protocol::types; use pyo3::{ + sync::GILOnceCell, types::{ PyAnyMethods, PyBool, PyBytes, PyDate, PyDateTime, PyDict, PyDictMethods, PyFloat, PyInt, - PyList, PyListMethods, PyString, PyTime, PyTuple, PyTypeMethods, + PyList, PyListMethods, PyString, PyTime, PyTuple, PyType, PyTypeMethods, }, - Bound, Py, PyAny, Python, ToPyObject, + Bound, Py, PyAny, PyObject, PyResult, Python, ToPyObject, }; use tokio_postgres::{ types::{to_sql_checked, Type}, @@ -28,8 +30,34 @@ use crate::{ }, }; +static DECIMAL_CLS: GILOnceCell> = GILOnceCell::new(); + 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_bound("decimal")? + .getattr("Decimal")? + .downcast_into()?; + Ok(type_object.unbind()) + }) + .map(|ty| ty.bind(py)) +} + +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) + } +} + /// Additional type for types come from Python. /// /// It's necessary because we need to pass this @@ -496,7 +524,7 @@ fn postgres_bytes_to_py( .to_object(py)), // // ---------- String Types ---------- // // Convert TEXT and VARCHAR type into String, then into str - Type::TEXT | Type::VARCHAR => Ok(_composite_field_postgres_to_py::>( + Type::TEXT | Type::VARCHAR | Type::XML => Ok(_composite_field_postgres_to_py::>( type_, buf, is_simple, )? .to_object(py)), @@ -592,13 +620,22 @@ fn postgres_bytes_to_py( Ok(py.None().to_object(py)) } } + Type::NUMERIC => { + if let Some(money_) = _composite_field_postgres_to_py::>( + type_, buf, is_simple, + )? { + Ok(InnerDecimal(money_).to_object(py)) + } else { + Ok(py.None().to_object(py)) + } + } // ---------- Array Text Types ---------- Type::BOOL_ARRAY => Ok(_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 => Ok(_composite_field_postgres_to_py::< + Type::TEXT_ARRAY | Type::VARCHAR_ARRAY | Type::XML_ARRAY => Ok(_composite_field_postgres_to_py::< Option>, >(type_, buf, is_simple)? .to_object(py)), From a43dc204ea710c0eb19756b2686ac5902d490fa9 Mon Sep 17 00:00:00 2001 From: "chandr-andr (Kiselev Aleksandr)" Date: Fri, 14 Jun 2024 22:58:06 +0200 Subject: [PATCH 2/3] Added support for NUMERIC and MONEY types Signed-off-by: chandr-andr (Kiselev Aleksandr) --- src/value_converter.rs | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/value_converter.rs b/src/value_converter.rs index eafd7b1f..c2d45d63 100644 --- a/src/value_converter.rs +++ b/src/value_converter.rs @@ -46,6 +46,10 @@ fn get_decimal_cls(py: Python<'_>) -> PyResult<&Bound<'_, PyType>> { .map(|ty| ty.bind(py)) } +/// 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 { @@ -90,6 +94,7 @@ pub enum PythonDTO { PyJson(Value), PyMacAddr6(MacAddr6), PyMacAddr8(MacAddr8), + PyDecimal(Decimal), PyCustomType(Vec), } @@ -126,6 +131,7 @@ impl PythonDTO { 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), _ => Err(RustPSQLDriverError::PyToRustValueConversionError( "Can't process array type, your type doesn't have support yet".into(), )), @@ -265,6 +271,9 @@ impl ToSql for PythonDTO { 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)?; + } } if return_is_null_true { Ok(tokio_postgres::types::IsNull::Yes) @@ -543,7 +552,7 @@ fn postgres_bytes_to_py( _composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py), ), // Convert BigInt into i64, then into int - Type::INT8 => Ok( + Type::INT8 | Type::MONEY => Ok( _composite_field_postgres_to_py::>(type_, buf, is_simple)?.to_object(py), ), // Convert REAL into f32, then into float @@ -621,13 +630,12 @@ fn postgres_bytes_to_py( } } Type::NUMERIC => { - if let Some(money_) = _composite_field_postgres_to_py::>( + if let Some(numeric_) = _composite_field_postgres_to_py::>( type_, buf, is_simple, )? { - Ok(InnerDecimal(money_).to_object(py)) - } else { - Ok(py.None().to_object(py)) + return Ok(InnerDecimal(numeric_).to_object(py)); } + Ok(py.None().to_object(py)) } // ---------- Array Text Types ---------- Type::BOOL_ARRAY => Ok(_composite_field_postgres_to_py::>>( @@ -651,7 +659,7 @@ fn postgres_bytes_to_py( )? .to_object(py)), // Convert ARRAY of BigInt into Vec, then into list[int] - Type::INT8_ARRAY => Ok(_composite_field_postgres_to_py::>>( + Type::INT8_ARRAY | Type::MONEY_ARRAY => Ok(_composite_field_postgres_to_py::>>( type_, buf, is_simple, )? .to_object(py)), @@ -723,6 +731,18 @@ fn postgres_bytes_to_py( None => Ok(py.None().to_object(py)), } } + Type::NUMERIC_ARRAY => { + if let Some(numeric_array) = _composite_field_postgres_to_py::>>( + type_, buf, is_simple, + )? { + let py_list = PyList::empty_bound(py); + for numeric_ in numeric_array { + py_list.append(InnerDecimal(numeric_).to_object(py))?; + } + return Ok(py_list.to_object(py)) + }; + Ok(py.None().to_object(py)) + }, _ => Err(RustPSQLDriverError::RustToPyValueConversionError( format!("Cannot convert {type_} into Python type, please look at the custom_decoders functionality.") )), From 986752046486f7b341f39c00a9af8dae28d099b8 Mon Sep 17 00:00:00 2001 From: "chandr-andr (Kiselev Aleksandr)" Date: Wed, 19 Jun 2024 19:19:11 +0200 Subject: [PATCH 3/3] Added tests for NUMERIC and MONEY types Signed-off-by: chandr-andr (Kiselev Aleksandr) --- python/psqlpy/_internal/extra_types.pyi | 10 ++++++++++ python/psqlpy/extra_types.py | 2 ++ python/tests/test_value_converter.py | 20 ++++++++++++++++++++ src/exceptions/rust_errors.rs | 5 ++++- src/extra_types.rs | 2 ++ src/value_converter.rs | 22 +++++++++++++++++++--- 6 files changed, 57 insertions(+), 4 deletions(-) diff --git a/python/psqlpy/_internal/extra_types.pyi b/python/psqlpy/_internal/extra_types.pyi index bd6350b5..bc7e6660 100644 --- a/python/psqlpy/_internal/extra_types.pyi +++ b/python/psqlpy/_internal/extra_types.pyi @@ -32,6 +32,16 @@ class BigInt: - `inner_value`: int object. """ +class Money: + """Represent `MONEY` in PostgreSQL and `i64` in Rust.""" + + def __init__(self: Self, inner_value: int) -> None: + """Create new instance of class. + + ### Parameters: + - `inner_value`: int object. + """ + class Float32: """Represents `FLOAT4` in `PostgreSQL` and `f32` in Rust.""" diff --git a/python/psqlpy/extra_types.py b/python/psqlpy/extra_types.py index d332c993..7f472975 100644 --- a/python/psqlpy/extra_types.py +++ b/python/psqlpy/extra_types.py @@ -3,6 +3,7 @@ Float32, Float64, Integer, + Money, PyCustomType, PyJSON, PyJSONB, @@ -26,4 +27,5 @@ "PyCustomType", "Float32", "Float64", + "Money", ] diff --git a/python/tests/test_value_converter.py b/python/tests/test_value_converter.py index d66cd605..9c6140d3 100644 --- a/python/tests/test_value_converter.py +++ b/python/tests/test_value_converter.py @@ -1,5 +1,6 @@ import datetime import uuid +from decimal import Decimal from enum import Enum from ipaddress import IPv4Address from typing import Any, Dict, List, Union @@ -15,6 +16,7 @@ Float32, Float64, Integer, + Money, PyJSON, PyJSONB, PyMacAddr6, @@ -72,10 +74,18 @@ async def test_as_class( ("BYTEA", b"Bytes", [66, 121, 116, 101, 115]), ("VARCHAR", "Some String", "Some String"), ("TEXT", "Some String", "Some String"), + ( + "XML", + """Manual...""", + """Manual...""", + ), ("BOOL", True, True), ("INT2", SmallInt(12), 12), ("INT4", Integer(121231231), 121231231), ("INT8", BigInt(99999999999999999), 99999999999999999), + ("MONEY", BigInt(99999999999999999), 99999999999999999), + ("MONEY", Money(99999999999999999), 99999999999999999), + ("NUMERIC(5, 2)", Decimal("120.12"), Decimal("120.12")), ("FLOAT4", 32.12329864501953, 32.12329864501953), ("FLOAT4", Float32(32.12329864501953), 32.12329864501953), ("FLOAT8", Float64(32.12329864501953), 32.12329864501953), @@ -145,6 +155,16 @@ async def test_as_class( [BigInt(99999999999999999), BigInt(99999999999999999)], [99999999999999999, 99999999999999999], ), + ( + "MONEY ARRAY", + [Money(99999999999999999), Money(99999999999999999)], + [99999999999999999, 99999999999999999], + ), + ( + "NUMERIC(5, 2) ARRAY", + [Decimal("121.23"), Decimal("188.99")], + [Decimal("121.23"), Decimal("188.99")], + ), ( "FLOAT4 ARRAY", [32.12329864501953, 32.12329864501953], diff --git a/src/exceptions/rust_errors.rs b/src/exceptions/rust_errors.rs index 035bd867..faddc854 100644 --- a/src/exceptions/rust_errors.rs +++ b/src/exceptions/rust_errors.rs @@ -75,6 +75,8 @@ pub enum RustPSQLDriverError { RustMacAddrConversionError(#[from] macaddr::ParseError), #[error("Cannot execute future in Rust: {0}")] RustRuntimeJoinError(#[from] JoinError), + #[error("Cannot convert python Decimal into rust Decimal")] + DecimalConversionError(#[from] rust_decimal::Error), } impl From for pyo3::PyErr { @@ -92,7 +94,8 @@ impl From for pyo3::PyErr { RustPSQLDriverError::RustToPyValueConversionError(_) => { RustToPyValueMappingError::new_err((error_desc,)) } - RustPSQLDriverError::PyToRustValueConversionError(_) => { + RustPSQLDriverError::PyToRustValueConversionError(_) + | RustPSQLDriverError::DecimalConversionError(_) => { PyToRustValueMappingError::new_err((error_desc,)) } RustPSQLDriverError::ConnectionPoolConfigurationError(_) => { diff --git a/src/extra_types.rs b/src/extra_types.rs index 01c0d968..2eaab79e 100644 --- a/src/extra_types.rs +++ b/src/extra_types.rs @@ -44,6 +44,7 @@ macro_rules! build_python_type { build_python_type!(SmallInt, i16); build_python_type!(Integer, i32); build_python_type!(BigInt, i64); +build_python_type!(Money, i64); build_python_type!(Float32, f32); build_python_type!(Float64, f64); @@ -189,6 +190,7 @@ pub fn extra_types_module(_py: Python<'_>, pymod: &Bound<'_, PyModule>) -> PyRes pymod.add_class::()?; pymod.add_class::()?; pymod.add_class::()?; + pymod.add_class::()?; pymod.add_class::()?; pymod.add_class::()?; pymod.add_class::()?; diff --git a/src/value_converter.rs b/src/value_converter.rs index c2d45d63..e35a1c46 100644 --- a/src/value_converter.rs +++ b/src/value_converter.rs @@ -25,8 +25,8 @@ use crate::{ additional_types::{RustMacAddr6, RustMacAddr8}, exceptions::rust_errors::{RustPSQLDriverError, RustPSQLDriverPyResult}, extra_types::{ - BigInt, Float32, Float64, Integer, PyCustomType, PyJSON, PyJSONB, PyMacAddr6, PyMacAddr8, - PyText, PyVarChar, SmallInt, + BigInt, Float32, Float64, Integer, Money, PyCustomType, PyJSON, PyJSONB, PyMacAddr6, + PyMacAddr8, PyText, PyVarChar, SmallInt, }, }; @@ -83,6 +83,7 @@ pub enum PythonDTO { PyIntU64(u64), PyFloat32(f32), PyFloat64(f64), + PyMoney(i64), PyDate(NaiveDate), PyTime(NaiveTime), PyDateTime(NaiveDateTime), @@ -122,6 +123,7 @@ impl PythonDTO { 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), @@ -231,7 +233,7 @@ impl ToSql for PythonDTO { } PythonDTO::PyIntI16(int) => out.put_i16(*int), PythonDTO::PyIntI32(int) => out.put_i32(*int), - PythonDTO::PyIntI64(int) => out.put_i64(*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), @@ -323,6 +325,7 @@ pub fn convert_parameters(parameters: Py) -> RustPSQLDriverPyResult) -> RustPSQLDriverPyResult { + println!("{}", parameter.get_type().name()?); if parameter.is_none() { return Ok(PythonDTO::PyNone); } @@ -389,6 +392,12 @@ pub fn py_to_rust(parameter: &pyo3::Bound<'_, PyAny>) -> RustPSQLDriverPyResult< )); } + if parameter.is_instance_of::() { + return Ok(PythonDTO::PyMoney( + parameter.extract::()?.retrieve_value(), + )); + } + if parameter.is_instance_of::() { return Ok(PythonDTO::PyIntI32(parameter.extract::()?)); } @@ -480,6 +489,13 @@ pub fn py_to_rust(parameter: &pyo3::Bound<'_, PyAny>) -> RustPSQLDriverPyResult< )?)); } + if parameter.get_type().name()? == "decimal.Decimal" { + println!("{}", parameter.str()?.extract::<&str>()?); + return Ok(PythonDTO::PyDecimal(Decimal::from_str_exact( + parameter.str()?.extract::<&str>()?, + )?)); + } + if let Ok(id_address) = parameter.extract::() { return Ok(PythonDTO::PyIpAddress(id_address)); }