Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/changes/newsfragments/8518.improved
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Adding metadata that SQLite cannot store in a column now fails with a message naming the tag and its type.

A nested dict or a sequence previously surfaced as ``sqlite3.ProgrammingError: Error binding parameter 1:
type 'list' is not supported``, which said nothing about which tag was at fault. ``validate_dynamic_column_data``
now rejects such values with a ``TypeError`` suggesting serialization, alongside its existing checks for invalid
tags and ``None``. Values that NumPy registers a SQLite adapter for, such as scalars and arrays, are unaffected.
32 changes: 32 additions & 0 deletions src/qcodes/dataset/sqlite/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from __future__ import annotations

import contextlib
import datetime
import logging
import sqlite3
Expand Down Expand Up @@ -1865,6 +1866,24 @@ def get_metadata_from_run_id(conn: AtomicConnection, run_id: int) -> dict[str, A
return metadata


def _is_storable_in_column(val: Any) -> bool:
"""
Return whether SQLite can store ``val`` in a column.

Rather than comparing against a fixed list of types, this asks SQLite
itself whether it can bind the value, so any type with a registered
adapter -- NumPy scalars and arrays, for instance -- is still accepted.
The statement is a bare ``SELECT`` against a throwaway in-memory
connection, so nothing is written anywhere.
"""
with contextlib.closing(sqlite3.connect(":memory:")) as probe:
try:
probe.execute("SELECT ?", (val,))
except (sqlite3.InterfaceError, sqlite3.ProgrammingError):
return False
return True


def validate_dynamic_column_data(data: Mapping[str, Any]) -> None:
"""
Validate the given dicts tags and values. Note that None is not a valid
Expand All @@ -1874,6 +1893,12 @@ def validate_dynamic_column_data(data: Mapping[str, Any]) -> None:
Args:
data: the metadata mapping (tags to values)

Raises:
KeyError: if a tag is not a valid SQLite column name.
ValueError: if a value is None.
TypeError: if a value cannot be stored in a SQLite column, such as a
nested dict or a sequence.

"""
for tag, val in data.items():
if not tag.isidentifier():
Expand All @@ -1885,6 +1910,13 @@ def validate_dynamic_column_data(data: Mapping[str, Any]) -> None:
raise ValueError(
f"Tag {tag} has value None. That is not a valid metadata value!"
)
if not _is_storable_in_column(val):
raise TypeError(
f"Tag {tag} has value of type {type(val).__name__}. That is "
"not a valid metadata value. Note that a column stores a single SQLite "
"value, so a nested dict or a sequence has to be serialized "
"first, for example with json.dumps."
)


def insert_data_in_dynamic_columns(
Expand Down
17 changes: 17 additions & 0 deletions tests/dataset/test_dataset_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,23 @@ def test_metadata(experiment, request: FixtureRequest) -> None:
ds1.add_metadata(good_tag, None)
assert error_caused_by(e2, none_value_msg)

# A column holds a single SQLite value, so anything nested has to say so
# rather than surfacing as a bare sqlite3 binding error. See issue #1444.
for bad_value in ({"b": 1}, [1, 2], (1, 2), {1, 2}):
nested_value_msg = (
f"Tag {good_tag} has value of type {type(bad_value).__name__}. "
"That is not a valid metadata value"
)
with pytest.raises(
RuntimeError, match="Rolling back due to unhandled exception"
) as e3:
ds1.add_metadata(good_tag, bad_value)
assert error_caused_by(e3, nested_value_msg)

# Values NumPy registers an adapter for must keep working.
ds1.add_metadata("np_scalar", np.int64(3))
assert ds1.metadata["np_scalar"] == 3


def test_the_same_dataset_as(some_interdeps, experiment) -> None:
ds = DataSet()
Expand Down
Loading