Skip to content

Load mergeExperiments variation .dat files into ApiDB variation tables#110

Open
jbrestel wants to merge 19 commits into
masterfrom
variation-dat-loader
Open

Load mergeExperiments variation .dat files into ApiDB variation tables#110
jbrestel wants to merge 19 commits into
masterfrom
variation-dat-loader

Conversation

@jbrestel

Copy link
Copy Markdown
Member

Summary

Adds a GUS plugin that loads the mergeExperiments (dnaseq-nextflow) variation outputs — variationFeature.dat, transcript_product.dat, snpeff.dat — into ApiDB.VariationFeature, VariationTranscriptProduct, and VariationEffect.

  • ApiCommonData::Load::VariationLoader — a pure module (no GUS, no DBI) defining the canonical column orders and the three file transforms. Unit-testable without a database.
  • ApiCommonData::Load::Plugin::InsertVariationFeatures — resolves the external database release, builds an organism-scoped transcript source_id → na_feature_id map, validates that every sequence id belongs to the organism's genome, transforms each file to a temp file, and loads all three in a single psql transaction (BEGIN; DELETE; \copy ×3; COMMIT; with ON_ERROR_STOP=1).

Key design points

  • One transaction across a parent table and two FK-children (ON DELETE CASCADE). A failure in any \copy rolls back the whole load, so a failed reload leaves the prior data intact rather than a half-populated set. Verified.
  • source_id = Variant_<sequence_source_id>_<location> — a stable, opaque identifier. A constant prefix, deliberately not variant_type (which is strain-set-derived and would churn the id across re-merges).
  • Undo via undoPreprocess, not undoTables — these tables carry no row_alg_invocation_id, so undo recovers extDbRlsSpec from core.AlgorithmParam and deletes by external_database_release_id (children cascade).
  • Intergenic snpeff rows (empty transcript_id, ~60% of rows) load a NULL na_feature_id; a non-empty but unresolvable transcript id is fatal.

Companion change (separate repo)

Requires ApidbSchema branch dnaseq-tables: adds VariationFeature.source_id VARCHAR(100) NOT NULL UNIQUE and drops VariationTranscriptProduct.downstream_of_frameshift_strain_ids. That branch needs its own PR/merge.

Test Plan

  • Unit: Load/t/variationLoader.t — 34 assertions, no DB (transforms, header parsing, source_id, fatal-vs-NULL semantics, and full-row column-mapping assertions guarding the column-list↔transform lockstep).
  • Unit: Load/t/insertVariationFeatures_unit.t — 4 assertions, no DB (single-line \copy output, schema-identifier guard).
  • Integration: Load/t/insertVariationFeatures_integration.t — 10 assertions against a live Postgres stub schema; loads the full sample dataset (1504 / 781 / 1978 rows), verifies the intergenic NULL split (797 non-null), and the cascade-delete undo path.
  • Not automated: the full ga plugin run needs a workflow context and an organism present in the target DB (the test DB has no L. major). Documented in the design spec for whoever has such an instance.

Design spec and implementation plan are in docs/superpowers/specs/ and docs/superpowers/plans/.

🤖 Generated with Claude Code

jbrestel and others added 19 commits July 10, 2026 11:24
Loader for variationFeature.dat / transcript_product.dat / snpeff.dat into
ApiDB.VariationFeature / VariationTranscriptProduct / VariationEffect.

Records two schema changes for ApidbSchema branch dnaseq-tables: add
VariationFeature.source_id (constant "Variant_" prefix, not variant_type,
which is strain-set-derived and would churn across re-merges), and drop
VariationTranscriptProduct.downstream_of_frameshift_strain_ids (dangling
reference into unloaded sample.dat).

Notes that undoTables cannot work on these tables (no core.TableInfo, no
housekeeping columns), so the loader owns an explicit delete-by-extDbRlsId
reload path; and that the (sequence_source_id, location) PK admits only one
variation dataset per genome.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation

undoTables() cannot work (no row_alg_invocation_id), but GUS::Community::Plugin::Undo
exposes an undocumented undoPreprocess($dbh, $algInvocationIds) hook that runs before
core.AlgorithmParam is deleted. Recover extDbRlsSpec from core.AlgorithmParam, as
InsertEdaStudyFromArtifacts does, then delete by external_database_release_id and let
ON DELETE CASCADE clear the children. The Undo.pm instance is fresh and uninitialized,
so getArg() and getExtDbRlsId() are unavailable.

Also records verification of the design against unidb_shu_a: a prototype transform
loaded 1504/781/1978 rows into the new jbrestel stub schema in one transaction,
re-ran idempotently, rolled back fully when the third COPY hit an FK violation, and
cascade-deleted to 0/0/0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sumption

Genomic sequence source_ids are unique across organisms (verified: zero dups in
dots.ExternalNaSequence for all 8 organisms in unidb_shu_a), so Variant_<seq>_<loc>
is unique. But this is a data property, not an enforced one -- dots.NaSequence in a
UniDB build has zero indexes and zero constraints. The real guarantee is our own
UNIQUE(source_id) on VariationFeature, which makes a collision a loud failure.

Also accepts one-variation-dataset-per-genome (implied by the composite PK) and
--targetSchema as a test affordance.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 tasks: ApidbSchema DDL change, pure VariationLoader module (column defs +
3 transforms, TDD), the InsertVariationFeatures plugin (args, DB queries,
single-transaction psql load, run() wiring, undoPreprocess), and an integration
test against the jbrestel stub schema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Source .pm files are flat but declare full ApiCommonData::Load:: packages, so tests
must 'use lib $GUS_HOME/lib/perl' against the built copy, and each .pm must be cp-synced
into $GUS_HOME before running. The prior 'use lib $PROJECT_HOME/.../lib/perl' and
'build ApiCommonData -append' commands were both wrong. Also record Test::Exception as a
repo test dep and add ~/perl5 to PERL5LIB.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…name count)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lidation

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- loadAll: use list-form system() + PGPASSWORD instead of a password-bearing
  connection URI through the shell (fixes misparse/leak if the password or a
  path contains shell/URI metacharacters); decode wait status to exit code.
- Guard targetSchema as a bare identifier before interpolating into SQL (run()
  and undoPreprocess).
- transformFile: check close() on the output handle so a truncated temp file
  (e.g. disk full) fails loudly instead of under-loading silently.
- Tests: add full-row column-mapping assertions for all three transforms
  (guards the column-list<->transform lockstep that count checks alone miss);
  add a DB-free plugin unit test pinning copyCommand's single-line output and
  validSchema.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Make the live-DB integration test opt-in (VARIATION_DB_TEST=1) and pull
all connection details from the environment (host, port, db, schema,
sample-data dir) with the previous hard-coded values as defaults. This
keeps CI and teammates green by default while preserving the end-to-end
\copy load coverage on demand, and removes the pin to one developer's box
and the jbrestel schema.

Also drop the unit test's stale reference to the integration test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The integration test no longer hard-codes the jbrestel schema, so the
arg help text need not mention it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.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