Skip to content

Refactor split raw data storage into a DataSetInSeparateSqliteDbFile subclass - #406

Open
astafan8 wants to merge 1 commit into
feature/split-raw-data-sqlitefrom
feature/split-raw-data-subclass
Open

Refactor split raw data storage into a DataSetInSeparateSqliteDbFile subclass#406
astafan8 wants to merge 1 commit into
feature/split-raw-data-sqlitefrom
feature/split-raw-data-subclass

Conversation

@astafan8

Copy link
Copy Markdown
Owner

Summary

Stacked on top of microsoft#8219 (targets feature/split-raw-data-sqlite, not main). Implements the subclass option from the "class structure" idea discussion: move the split raw data storage behaviour out of the config-flag conditionals inside DataSet into a dedicated DataSetInSeparateSqliteDbFile(DataSet) subclass, selected at the factory boundary.

This is a prototype for review — it implements the architecture I described in the idea thread so it can be evaluated as an isolated diff.

What changes

Generic seam in the base DataSet

  • DataSet gains a small set of overridable hooks whose base implementations reproduce today's main-DB behaviour (all no-ops for a plain DataSet):
    • _setup_results_backend_on_load(read_only) / _setup_results_backend_on_new_run() / _setup_results_backend_on_start()
    • class attribute _creates_results_table_in_main_db = True
  • The generic "results may live on a separate connection" routing (_data_conn, the get_parameter_data bypass, the add_results background-writer item) stays in the base and only activates once _raw_data_conn is populated. The backend-agnostic parts added earlier (_results_table_exists, tolerant number_of_results/__len__, deferred subscribers) also stay in the base.

The SQLite backend subclass

  • DataSetInSeparateSqliteDbFile(DataSet) overrides only the hooks: create/connect the per-dataset <guid>.db file, record raw_data_db_path, flip _creates_results_table_in_main_db = False. All is_raw_data_storage_enabled() conditionals leave DataSet.

Class selection at the factory boundary

  • New runs: new_data_set() and Measurement instantiate the subclass when the feature is enabled.
  • Existing runs: a new _load_dataset_from_run_id() helper picks the subclass when the run records a raw_data_db_path. Routed through experiment_container.data_set(), extract_runs_into_db, _copy_dataset_as_is, and _get_datasetprotocol_from_guid.

Tradeoff surfaced by this refactor (worth a look)

Moving class selection to a factory means direct DataSet(run_id=...) construction on a split run no longer auto-detects the raw backend — you must go through _load_dataset_from_run_id / load_by_id. In the current PR microsoft#8219 (config-flag design), DataSet.__init__ auto-detects from the DB row, so direct construction "just works" everywhere.

I routed every construction site in the library through the factory, and updated the make_shadow_dataset test helper. But this is a real behavioural difference for any external code that constructs DataSet(run_id=...) directly. A composition/strategy design (a ResultsBackend object chosen inside DataSet.__init__) would keep the subclass-free base and preserve auto-detection — arguably the best of both. I'm flagging this so we can decide between:

  1. this subclass approach (clean class hierarchy, but factory-on-load), and
  2. a results-backend strategy object (auto-detection preserved, composition over inheritance).

Either way, this cleanly sets up the future non-SQLite backends goal (a later DataSetInSeparateFile base, or additional ResultsBackend implementations).

Verification

  • test_raw_data_storage.py (+ new tests asserting new_data_set/load_by_id return the subclass when enabled and a plain DataSet when disabled), test_database_extract_runs.py, test_export_datasets_and_create_metadata_db.py: 64 passed, 1 skipped.
  • test_dataset_basic.py, dond/, test_measurement_extensions.py: 333 passed in default mode.
  • Full tests/dataset suite in default mode: 1064 passed, 35 skipped — no regressions.
  • Validated with split storage globally enabled (temporary local harness, not part of this PR): same expected/inherent failure set as feat: split raw data storage into per-dataset SQLite files microsoft/Qcodes#8219's baseline (disabled-mode tests, low-level queries.get_parameter_data(ds.conn, ...) bypass tests, db-overview/guid-scan tests that inherently assume data in the main DB). One additional order-dependent flake under the randomized harness (test_do1d_additional_setpoints_shape) passes in isolation and in default mode.
  • Pyright: 0 errors. Ruff check + format: pass.

…bclass

Move the per-dataset SQLite storage behaviour out of the config-flag
conditionals in DataSet and into a dedicated DataSetInSeparateSqliteDbFile
subclass, selected at the factory boundary.

- Base DataSet gains generic results-backend hooks (no-ops by default):
  _setup_results_backend_on_load/_on_new_run/_on_start, plus the class
  attribute _creates_results_table_in_main_db. The generic 'results may live
  on a separate connection' routing (_data_conn, get_parameter_data,
  add_results) stays in the base and activates only when _raw_data_conn is
  populated.
- DataSetInSeparateSqliteDbFile(DataSet) overrides only those hooks to create/
  connect the per-dataset file and record raw_data_db_path.
- Class selection moves to the factory boundary: new_data_set() and Measurement
  instantiate the subclass when the feature is enabled; a new
  _load_dataset_from_run_id() helper picks the subclass for existing runs that
  record a raw_data_db_path. Routed through experiment_container.data_set,
  extract_runs, and _get_datasetprotocol_from_guid.
- Note: direct DataSet(run_id=...) construction on a split run no longer
  auto-detects the raw backend; use _load_dataset_from_run_id / load_by_id.
  Updated make_shadow_dataset accordingly.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant