Skip to content

Commit 82c66dd

Browse files
authored
Merge pull request #162 from psqlpy-python/f/JSONB-without-limits
JSON and JSONB can handle different sized arrays inside
2 parents 1562a07 + 437b47a commit 82c66dd

File tree

3 files changed

+79
-31
lines changed

3 files changed

+79
-31
lines changed

python/tests/test_value_converter.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,26 @@ async def test_as_class(
204204
JSON([1, "1", 1.0]),
205205
[1, "1", 1.0],
206206
),
207+
(
208+
"JSONB",
209+
[[1, 2], [3]],
210+
[[1, 2], [3]],
211+
),
212+
(
213+
"JSON",
214+
[[1, 2], [3]],
215+
[[1, 2], [3]],
216+
),
217+
(
218+
"JSONB",
219+
{"nested": [[1, 2], [3]]},
220+
{"nested": [[1, 2], [3]]},
221+
),
222+
(
223+
"JSON",
224+
{"nested": [[1, 2], [3]]},
225+
{"nested": [[1, 2], [3]]},
226+
),
207227
(
208228
"MACADDR",
209229
MacAddr6("08:00:2b:01:02:03"),

src/value_converter/from_python.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use crate::{
2222

2323
use super::{
2424
additional_types::NonePyType,
25+
models::serde_value::build_serde_value,
2526
traits::{ToPythonDTO, ToPythonDTOArray},
2627
};
2728

@@ -214,6 +215,23 @@ pub fn from_python_typed(
214215
}
215216

216217
if parameter.is_instance_of::<PyList>() | parameter.is_instance_of::<PyTuple>() {
218+
match *type_ {
219+
Type::JSONB => {
220+
if parameter.is_instance_of::<extra_types::JSONB>() {
221+
return <extra_types::JSONB as ToPythonDTO>::to_python_dto(parameter);
222+
}
223+
let v = build_serde_value(parameter)?;
224+
return Ok(PythonDTO::PyJsonb(v));
225+
}
226+
Type::JSON => {
227+
if parameter.is_instance_of::<extra_types::JSON>() {
228+
return <extra_types::JSON as ToPythonDTO>::to_python_dto(parameter);
229+
}
230+
let v = build_serde_value(parameter)?;
231+
return Ok(PythonDTO::PyJson(v));
232+
}
233+
_ => {}
234+
}
217235
return <extra_types::PythonArray as ToPythonDTOArray>::to_python_dto(
218236
parameter,
219237
type_.clone(),
@@ -602,7 +620,7 @@ fn convert_py_to_rust_coord_values(parameters: Vec<Py<PyAny>>) -> PSQLPyResult<V
602620
let parameter_bind = one_parameter.bind(gil);
603621

604622
if !parameter_bind.is_instance_of::<PyFloat>()
605-
& !parameter_bind.is_instance_of::<PyInt>()
623+
&& !parameter_bind.is_instance_of::<PyInt>()
606624
{
607625
return Err(RustPSQLDriverError::PyToRustValueConversionError(
608626
"Incorrect types of coordinate values. It must be int or float".into(),
@@ -699,7 +717,7 @@ pub fn build_geo_coords(
699717
let number_of_coords = result_vec.len();
700718
let allowed_length = allowed_length_option.unwrap_or_default();
701719

702-
if (allowed_length != 0) & (number_of_coords != allowed_length) {
720+
if (allowed_length != 0) && (number_of_coords != allowed_length) {
703721
return Err(RustPSQLDriverError::PyToRustValueConversionError(format!(
704722
"Invalid number of coordinates for this geo type, allowed {allowed_length}, got: {number_of_coords}"
705723
)));
@@ -730,7 +748,7 @@ pub fn build_flat_geo_coords(
730748
let parameters = py_sequence_to_rust(bind_py_parameters)?;
731749
let parameters_length = parameters.len();
732750

733-
if (allowed_length != 0) & (parameters.len() != allowed_length) {
751+
if (allowed_length != 0) && (parameters.len() != allowed_length) {
734752
return Err(RustPSQLDriverError::PyToRustValueConversionError(format!(
735753
"Invalid number of values for this geo type, allowed {allowed_length}, got: {parameters_length}"
736754
)));
@@ -739,7 +757,7 @@ pub fn build_flat_geo_coords(
739757
let result_vec = convert_py_to_rust_coord_values(parameters)?;
740758

741759
let number_of_coords = result_vec.len();
742-
if (allowed_length != 0) & (number_of_coords != allowed_length) {
760+
if (allowed_length != 0) && (number_of_coords != allowed_length) {
743761
return Err(RustPSQLDriverError::PyToRustValueConversionError(format!(
744762
"Invalid number of values for this geo type, allowed {allowed_length}, got: {parameters_length}"
745763
)));

src/value_converter/models/serde_value.rs

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
use postgres_array::{Array, Dimension};
22
use postgres_types::FromSql;
3-
use serde_json::{json, Map, Value};
3+
use serde_json::{Map, Value};
44

55
use pyo3::{
6-
types::{PyAnyMethods, PyDict, PyDictMethods, PyList, PyListMethods},
6+
types::{PyAnyMethods, PyDict, PyDictMethods, PyList, PyListMethods, PyTuple, PyTupleMethods},
77
Bound, FromPyObject, IntoPyObject, PyAny, PyResult, Python,
88
};
99
use tokio_postgres::types::Type;
@@ -57,26 +57,38 @@ impl<'a> FromSql<'a> for InternalSerdeValue {
5757
}
5858
}
5959

60-
fn serde_value_from_list(_gil: Python<'_>, bind_value: &Bound<'_, PyAny>) -> PSQLPyResult<Value> {
61-
let py_list = bind_value.downcast::<PyList>().map_err(|e| {
62-
RustPSQLDriverError::PyToRustValueConversionError(format!(
63-
"Parameter must be a list, but it's not: {e}"
64-
))
65-
})?;
60+
fn serde_value_for_json_child(item: &Bound<'_, PyAny>) -> PSQLPyResult<Value> {
61+
if item.is_instance_of::<PyList>()
62+
|| item.is_instance_of::<PyTuple>()
63+
|| item.is_instance_of::<PyDict>()
64+
{
65+
build_serde_value(item)
66+
} else {
67+
Ok(from_python_untyped(item)?.to_serde_value()?)
68+
}
69+
}
6670

67-
let mut result_vec: Vec<Value> = Vec::with_capacity(py_list.len());
71+
fn serde_value_from_sequence(bind_value: &Bound<'_, PyAny>) -> PSQLPyResult<Value> {
72+
let mut result_vec: Vec<Value> = Vec::new();
6873

69-
for item in py_list.iter() {
70-
if item.is_instance_of::<PyList>() {
71-
let serde_value = build_serde_value(&item)?;
72-
result_vec.push(serde_value);
73-
} else {
74-
let python_dto = from_python_untyped(&item)?;
75-
result_vec.push(python_dto.to_serde_value()?);
74+
if let Ok(py_list) = bind_value.downcast::<PyList>() {
75+
result_vec.reserve(py_list.len());
76+
for item in py_list.iter() {
77+
result_vec.push(serde_value_for_json_child(&item)?);
78+
}
79+
} else if let Ok(py_tuple) = bind_value.downcast::<PyTuple>() {
80+
result_vec.reserve(py_tuple.len());
81+
for index in 0..py_tuple.len() {
82+
let item = py_tuple.get_item(index)?;
83+
result_vec.push(serde_value_for_json_child(&item)?);
7684
}
85+
} else {
86+
return Err(RustPSQLDriverError::PyToRustValueConversionError(
87+
"PyJSON array must be list or tuple.".to_string(),
88+
));
7789
}
7890

79-
Ok(json!(result_vec))
91+
Ok(Value::Array(result_vec))
8092
}
8193

8294
fn serde_value_from_dict(bind_value: &Bound<'_, PyAny>) -> PSQLPyResult<Value> {
@@ -96,8 +108,7 @@ fn serde_value_from_dict(bind_value: &Bound<'_, PyAny>) -> PSQLPyResult<Value> {
96108
))
97109
})?;
98110

99-
let value_dto = from_python_untyped(&value)?;
100-
serde_map.insert(key_str, value_dto.to_serde_value()?);
111+
serde_map.insert(key_str, serde_value_for_json_child(&value)?);
101112
}
102113

103114
Ok(Value::Object(serde_map))
@@ -110,16 +121,15 @@ fn serde_value_from_dict(bind_value: &Bound<'_, PyAny>) -> PSQLPyResult<Value> {
110121
#[allow(clippy::needless_pass_by_value)]
111122
#[allow(clippy::needless_return)]
112123
pub fn build_serde_value(value: &Bound<'_, PyAny>) -> PSQLPyResult<Value> {
113-
Python::with_gil(|gil| {
114-
if value.is_instance_of::<PyList>() {
115-
return serde_value_from_list(gil, value);
116-
} else if value.is_instance_of::<PyDict>() {
117-
return serde_value_from_dict(value);
118-
}
124+
if value.is_instance_of::<PyList>() || value.is_instance_of::<PyTuple>() {
125+
serde_value_from_sequence(value)
126+
} else if value.is_instance_of::<PyDict>() {
127+
serde_value_from_dict(value)
128+
} else {
119129
Err(RustPSQLDriverError::PyToRustValueConversionError(
120-
"PyJSON must be dict or list value.".to_string(),
130+
"PyJSON must be dict, list, or tuple value.".to_string(),
121131
))
122-
})
132+
}
123133
}
124134

125135
/// Convert Array of `PythonDTO`s to serde `Value`.

0 commit comments

Comments
 (0)