diff --git a/quasardb/convert/array.hpp b/quasardb/convert/array.hpp index 3eff9515..bc365b6b 100644 --- a/quasardb/convert/array.hpp +++ b/quasardb/convert/array.hpp @@ -184,6 +184,7 @@ struct convert_array static constexpr convert_array const delegate{}; +#if 1 [[nodiscard]] constexpr inline auto operator()() const noexcept { auto xform = [](value_type const & x) -> delegate_value_type { @@ -198,6 +199,22 @@ struct convert_array }; return ranges::views::transform(xform) | delegate(); }; +#else + [[nodiscard]] constexpr inline auto operator()() const noexcept + { + auto xform = [](delegate_value_type const & x) -> value_type { + if (Delegate::is_null(x)) + { + return To::null_value(); + } + else + { + return static_cast(x); + } + }; + return delegate() | ranges::views::transform(xform); + }; +#endif }; }; // namespace qdb::convert::detail diff --git a/quasardb/convert/value.hpp b/quasardb/convert/value.hpp index fb39e3ae..1722bccb 100644 --- a/quasardb/convert/value.hpp +++ b/quasardb/convert/value.hpp @@ -571,10 +571,29 @@ struct value_converter } }; +#if 1 template <> struct value_converter : public value_converter {}; +#else template <> +struct value_converter +{ + using dtype = traits::pyobject_dtype; + + value_converter delegate_{}; + + inline qdb_blob_t operator()(py::object const & x) const + { + if (dtype::is_null(x)) + { + return traits::null_value(); + } + + return delegate_(py::cast(x)); + } +}; +#endif template <> struct value_converter diff --git a/quasardb/masked_array.hpp b/quasardb/masked_array.hpp index 0de1479a..71623d3b 100644 --- a/quasardb/masked_array.hpp +++ b/quasardb/masked_array.hpp @@ -505,6 +505,22 @@ class masked_array py::array_t ret{ShapeContainer{xs.size()}}; bool * p_ret = static_cast(ret.mutable_data()); +#if 0 + if constexpr (std::is_same_v) + { + auto begin = static_cast(xs.data()); + auto end = begin + xs.size(); + + // NumPy object arrays store raw PyObject * values, not py::object wrappers. + for (auto cur = begin; cur != end; ++cur, ++p_ret) + { + *p_ret = (*cur == nullptr) || (*cur == Py_None); + } + + return mask::of_array(py::cast(ret)); + } +#endif + // The step_size is `1` for all fixed-width dtypes, but in case // of variable width dtypes, is, well, variable. py::ssize_t step_size = Dtype::stride_size(xs.itemsize()); diff --git a/quasardb/numpy/__init__.py b/quasardb/numpy/__init__.py index f712db63..d0b9c8cb 100644 --- a/quasardb/numpy/__init__.py +++ b/quasardb/numpy/__init__.py @@ -185,6 +185,7 @@ def _coerce_dtype( "Forced dtype provided for column '%s' = %s, but that column is not found in the table. Skipping...", k, ) + continue i = offsets[k] dtype_[i] = dt diff --git a/tests/test_convert.cpp b/tests/test_convert.cpp index 6f920e4c..0f9d4ffe 100644 --- a/tests/test_convert.cpp +++ b/tests/test_convert.cpp @@ -79,7 +79,7 @@ struct array_recode_cdtype_dispatch; { \ auto tmp = convert::point_array(input); \ auto tmp2 = convert::point_array(tmp); \ - output = input; \ + output = std::move(tmp2); \ } \ }; diff --git a/tests/test_convert_blob.py b/tests/test_convert_blob.py new file mode 100644 index 00000000..78cb9941 --- /dev/null +++ b/tests/test_convert_blob.py @@ -0,0 +1,50 @@ +from quasardb import test_convert as m + +import numpy as np +import numpy.ma as ma +import quasardb + +from utils import assert_indexed_arrays_equal + + +def _blob_recode(values): + idx = np.array( + [ + np.datetime64("2017-01-01T00:00:00", "ns"), + np.datetime64("2017-01-01T00:00:01", "ns"), + np.datetime64("2017-01-01T00:00:02", "ns"), + np.datetime64("2017-01-01T00:00:03", "ns"), + ] + ) + return m.test_array_recode(quasardb.ColumnType.Blob, np.dtype("O"), (idx, values)) + + +def test_blob_object_array_recode_roundtrip_bytes(): + values = np.array([b"alpha", b"beta", b"\x00gamma", b"delta"], dtype=np.object_) + result = _blob_recode(values) + assert_indexed_arrays_equal((result[0], values), result) + + +def test_blob_object_array_recode_roundtrip_none_values(): + values = np.array([b"alpha", None, b"gamma", None], dtype=np.object_) + result = _blob_recode(values) + assert_indexed_arrays_equal((result[0], values), result) + + +def test_blob_object_array_recode_roundtrip_masked_values(): + values = ma.masked_array( + data=np.array([b"alpha", b"beta", b"gamma", b"delta"], dtype=np.object_), + mask=[False, True, False, True], + ) + result = _blob_recode(values) + assert_indexed_arrays_equal((result[0], values), result) + + +def test_blob_object_array_recode_roundtrip_empty_bytes(): + values = np.array([b"", b"beta", b"", b"delta"], dtype=np.object_) + result = _blob_recode(values) + expected = ma.masked_array( + data=np.array([None, b"beta", None, b"delta"], dtype=np.object_), + mask=[True, False, True, False], + ) + assert_indexed_arrays_equal((result[0], expected), result)