Repository: man-group/ArcticDB (language: python)

Issue:
lib.list_versions('symbol', latest_only=True) throws InternalException if 'symbol' does not exist.
### Describe the bug

With an empty database, calling `list_versions('symbol')` returns an empty dictionary type `{}`. However with the `latest_only=True` option an `InternalException` error is thrown. These should behave the same.



### Steps/Code to Reproduce

```python
from arcticdb import Arctic
ac = Arctic('lmdb://path/to/database')
ac.create_library('data')
lib = ac['data']
lib.list_versions('symbol')
lib.list_versions('symbol', latest_only=True)
```

Output:

```python-traceback
---------------------------------------------------------------------------
InternalException                         Traceback (most recent call last)
Input In [103], in <cell line: 5>()
      3 lib = ac['data']
      4 lib.list_versions('symbol')
----> 5 lib.list_versions('symbol', latest_only=True)

File /pegasus-next-venv/lib/python3.8/site-packages/arcticdb/version_store/library.py:1263, in Library.list_versions(self, symbol, snapshot, latest_only, skip_snapshots)
   1221 def list_versions(
   1222     self,
   1223     symbol: Optional[str] = None,
   (...)
   1226     skip_snapshots: bool = False,
   1227 ) -> Dict[SymbolVersion, VersionInfo]:
   1228     """
   1229     Get the versions in this library, filtered by the passed in parameters.
   1230 
   (...)
   1261     ["my_snap"]
   1262     """
-> 1263     versions = self._nvs.list_versions(
   1264         symbol=symbol,
   1265         snapshot=snapshot,
   1266         latest_only=latest_only,
   1267         iterate_on_failure=False,
   1268         skip_snapshots=skip_snapshots,
   1269     )
   1270     return {
   1271         SymbolVersion(v["symbol"], v["version"]): VersionInfo(v["date"], v["deleted"], v["snapshots"])
   1272         for v in versions
   1273     }

File /pegasus-next-venv/lib/python3.8/site-packages/arcticdb/version_store/_store.py:1814, in NativeVersionStore.list_versions(self, symbol, snapshot, latest_only, iterate_on_failure, skip_snapshots)
   1811     log.warning("latest_only has no effect when snapshot is specified")
   1812     NativeVersionStore._warned_about_list_version_latest_only_and_snapshot = True
-> 1814 result = self.version_store.list_versions(symbol, snapshot, latest_only, iterate_on_failure, skip_snapshots)
   1815 return [
   1816     {
   1817         "symbol": version_result[0],
   (...)
   1823     for version_result in result
   1824 ]

InternalException: std::bad_optional_access(bad optional access)
```

### Expected Results

No error is thrown.

### OS, Python Version and ArcticDB Version

Python: 3.8.12 | packaged by conda-forge | (default, Jan 30 2022, 23:42:07) 
[GCC 9.4.0]
OS: Linux-3.10.0-1160.81.1.el7.x86_64-x86_64-with-glibc2.10
ArcticDB: 1.4.0


### Backend storage used

LMDB

### Additional Context

_No response_


Discussion / hints:

Fix patch:
diff --git a/cpp/arcticdb/version/version_store_api.cpp b/cpp/arcticdb/version/version_store_api.cpp
index 0afd60cbe8..746e767a00 100644
--- a/cpp/arcticdb/version/version_store_api.cpp
+++ b/cpp/arcticdb/version/version_store_api.cpp
@@ -212,14 +212,15 @@ VersionResultVector get_latest_versions_for_symbols(
     VersionResultVector res;
     std::unordered_set<std::pair<StreamId, VersionId>> unpruned_versions;
     for (auto &s_id: stream_ids) {
-            auto version_key = get_latest_undeleted_version(store, version_map, s_id, version_query, ReadOptions{}).value();
-            res.emplace_back(
-                s_id,
-                version_key.version_id(),
-                version_key.creation_ts(),
-                snapshots_for_symbol[{s_id, version_key.version_id()}],
-                false);
-
+            const auto& opt_version_key = get_latest_undeleted_version(store, version_map, s_id, version_query, ReadOptions{});
+            if (opt_version_key) {
+                res.emplace_back(
+                    s_id,
+                    opt_version_key->version_id(),
+                    opt_version_key->creation_ts(),
+                    snapshots_for_symbol[{s_id, opt_version_key->version_id()}],
+                    false);
+            }
     }
     std::sort(res.begin(), res.end(), VersionComp());
     return res;


Test patch:
diff --git a/python/tests/integration/arcticdb/test_arctic.py b/python/tests/integration/arcticdb/test_arctic.py
index 345ba8ebe3..c4e065ae89 100644
--- a/python/tests/integration/arcticdb/test_arctic.py
+++ b/python/tests/integration/arcticdb/test_arctic.py
@@ -452,6 +452,40 @@ def test_delete_version(arctic_library):
     assert lib["symbol"].metadata == {"very": "interesting"}
 
 
+def test_list_versions_write_append_update(arctic_library):
+    lib = arctic_library
+    # Note: can only update timeseries dataframes
+    index = pd.date_range(start="2000-01-01", freq="D", periods=3)
+    df = pd.DataFrame({"col1": [1, 2, 3], "col2": [4, 5, 6]}, index=index)
+    lib.write("symbol", df)
+    index_append = pd.date_range(start="2000-01-04", freq="D", periods=3)
+    df_append = pd.DataFrame({"col1": [7, 8, 9], "col2": [10, 11, 12]}, index=index_append)
+    lib.append("symbol", df_append)
+    index_update = pd.DatetimeIndex(["2000-01-03", "2000-01-05"])
+    df_update = pd.DataFrame({"col1": [13, 14], "col2": [15, 16]}, index=index_update)
+    lib.update("symbol", df_update)
+    assert_frame_equal(lib.read("symbol").data, pd.concat([df.iloc[:-1], df_update, df_append.iloc[[2]]]))
+    assert len(lib.list_versions("symbol")) == 3
+
+
+def test_list_versions_latest_only(arctic_library):
+    lib = arctic_library
+    df = pd.DataFrame({"col1": [1, 2, 3], "col2": [4, 5, 6]})
+    lib.write("symbol", df)
+    lib.write("symbol", df)
+    lib.write("symbol", df)
+    assert len(lib.list_versions("symbol", latest_only=True)) == 1
+
+
+def test_non_existent_list_versions_latest_only(arctic_library):
+    lib = arctic_library
+    assert len(lib.list_versions("symbol", latest_only=True)) == 0
+    df = pd.DataFrame({"col1": [1, 2, 3], "col2": [4, 5, 6]})
+    lib.write("symbol2", df)
+    lib.delete("symbol2")
+    assert len(lib.list_versions("symbol2", latest_only=True)) == 0
+
+
 def test_delete_version_with_snapshot(arctic_library):
     lib = arctic_library
     df = pd.DataFrame({"col1": [1, 2, 3], "col2": [4, 5, 6]})