Skip to content

Commit

Permalink
Bugfix/1841/maintain empty series names (#1983)
Browse files Browse the repository at this point in the history
#### Reference Issues/PRs
Fixes #1841 

#### What does this implement or fix?
Before this change, if a `Series` had an empty-string as a name, this
would be roundtripped as a `None`.
This introduces a `has_name` bool to the normalization metadata
protobuf, as a backwards-compatible way of effectively making the `name`
field optional.

The behaviour (which has been verified) can be summarised as follows:
```
Writer version | Series name | Protobuf name field | Protobuf has_name field | Series name read by <=5.0.0 | Series name read by this branch
---------------|-------------|---------------------|-------------------------|-----------------------------|--------------------------------
       <=5.0.0 |     "hello" |             "hello" |             Not present |                     "hello" |                         "hello"
       <=5.0.0 |          "" |                  "" |             Not present |                        None |                            None
       <=5.0.0 |        None |                  "" |             Not present |                        None |                            None
   This branch |     "hello" |             "hello" |                    True |                     "hello" |                         "hello"
   This branch |          "" |                  "" |                    True |                        None |                              ""
   This branch |        None |                  "" |                   False |                        None |                            None
```
  • Loading branch information
alexowens90 authored Nov 5, 2024
1 parent 84678ec commit 0c4949a
Show file tree
Hide file tree
Showing 3 changed files with 29 additions and 2 deletions.
3 changes: 3 additions & 0 deletions cpp/proto/arcticc/pb2/descriptors.proto
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ message NormalizationMetadata {

// Pandas series support arbitrary objects but restricting to a string seems reasonable
string name = 1;
// Backwards-compatible way of treating name as optional
bool has_name = 9;

oneof index_type {
PandasIndex index = 2;
Expand Down Expand Up @@ -190,6 +192,7 @@ message NormalizationMetadata {

map<string, ColumnName> col_names = 7;
PandasIndex columns = 8;
// Note that 9 is taken above by has_name, so the next added field should be 10
}

message PandasDataFrame {
Expand Down
9 changes: 7 additions & 2 deletions python/arcticdb/version_store/_normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -608,8 +608,10 @@ def normalize(self, item, string_max_len=None, dynamic_strings=False, coerce_col
empty_types=empty_types
)
norm.series.CopyFrom(norm.df)
if item.name:
if item.name is not None:
norm.series.common.name = _column_name_to_strings(item.name)
norm.series.common.has_name = True
# else protobuf bools default to False

return NormalizedInput(item=df, metadata=norm)

Expand All @@ -620,9 +622,12 @@ def denormalize(self, item, norm_meta):

series = pd.Series() if df.columns.empty else df.iloc[:, 0]

if norm_meta.common.name:
if len(norm_meta.common.name) or norm_meta.common.has_name:
series.name = norm_meta.common.name
else:
# Either the Series was written with a new client that understands the has_name field, and it was None, or
# the Series was written by an older client as either an empty string or None, we cannot tell, so maintain
# behaviour as it was before the has_name field was added
series.name = None

return series
Expand Down
19 changes: 19 additions & 0 deletions python/tests/unit/arcticdb/version_store/test_normalization.py
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,25 @@ def test_columns_names_timeframe(lmdb_version_store, sym):
assert tf == vit.data


@pytest.mark.parametrize("name", (None, "", "non_empty"))
def test_roundtrip_series_name(lmdb_version_store_v1, name):
lib = lmdb_version_store_v1
sym = "test_roundtrip_series_name"
series = pd.Series(np.arange(1), name=name)
lib.write(sym, series)
assert_series_equal(series, lib.read(sym).data)


@pytest.mark.parametrize("name", (None, "", "non_empty"))
def test_roundtrip_index_name(lmdb_version_store_v1, name):
lib = lmdb_version_store_v1
sym = "test_roundtrip_index_name"
df = pd.DataFrame({"col": [0]}, index=[pd.Timestamp(0)])
df.index.name = name
lib.write(sym, df)
assert_frame_equal(df, lib.read(sym).data)


def test_columns_names_series(lmdb_version_store, sym):
dr = pd.date_range("2020-01-01", "2020-12-31", name="date")
date_series = pd.Series(dr, index=dr)
Expand Down

0 comments on commit 0c4949a

Please sign in to comment.