Skip to content

VCF reader unit tests#3

Open
amd-pdebski wants to merge 39 commits into
biodatageeks:masterfrom
amd-pdebski:vcf_unit_tests
Open

VCF reader unit tests#3
amd-pdebski wants to merge 39 commits into
biodatageeks:masterfrom
amd-pdebski:vcf_unit_tests

Conversation

@amd-pdebski

@amd-pdebski amd-pdebski commented May 31, 2025

Copy link
Copy Markdown
Contributor

add simple unit tests and small mock files to test reading info, headers and file VCF contents

@amd-pdebski amd-pdebski force-pushed the vcf_unit_tests branch 10 times, most recently from ed86dff to a5bce8f Compare June 1, 2025 11:05
@mwiewior

mwiewior commented Jun 4, 2025

Copy link
Copy Markdown
Contributor

@pdebski21 pls rebase with master

mwiewior and others added 18 commits June 7, 2025 14:24
Signed-off-by: Piotr Dębski <piotrdebski@Piotrs-MacBook-Pro.local>
Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
…nt fetches

Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
…:nullable flag

Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
Signed-off-by: Piotr Dębski <ppdebski@interia.eu>
fix method name disable_vm_metadata -> disable_ec2_metadata
minor unused and not reachable values fixes
@amd-pdebski amd-pdebski changed the base branch from vcf to master June 7, 2025 12:41
mwiewior added a commit that referenced this pull request Feb 3, 2026
When tag_fields are explicitly passed to new_for_write() but those
fields lack metadata, insert_into() was silently ignoring them and
only using schema metadata. This caused data loss when constructing
schemas programmatically or via SQL.

Now merge explicitly provided tag_fields with metadata-derived tags
to ensure all requested tags are written to output files.

Fixes chatgpt-codex-connector P2 issues #3 and #4.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
mwiewior added a commit that referenced this pull request Feb 3, 2026
* feat: Add write support for BAM/SAM and CRAM formats

Implements comprehensive write functionality for BAM/SAM and CRAM file formats,
enabling users to write DataFusion query results to alignment files with full
metadata preservation and coordinate system conversion.

## BAM/SAM Write Support

### New Files
- writer.rs: SAM/BAM writer with auto-compression detection
- header_builder.rs: Reconstructs SAM headers from Arrow schema metadata
- serializer.rs: Converts Arrow RecordBatch to BAM records
- write_exec.rs: Physical execution plan for write operations
- examples/write_bam.rs: Comprehensive write examples

### Features
- Auto-detection from file extension (.sam → plain text, .bam → BGZF)
- Metadata preservation (headers, reference sequences, read groups, programs)
- Coordinate system conversion (0-based ↔ 1-based)
- Alignment tag support (NM, MD, AS, etc.)
- Round-trip compatibility

## CRAM Write Support

### New Files
- writer.rs: CRAM writer with reference sequence support
- header_builder.rs: CRAM-specific header building
- serializer.rs: Arrow RecordBatch to CRAM record conversion
- write_exec.rs: Physical execution plan with reference handling
- examples/write_cram.rs: CRAM write examples

### Features
- Reference FASTA support for optimal compression
- Reference validation (.fai index checking)
- Same serialization logic as BAM (noodles handles encoding)
- BAM ↔ CRAM conversion support

## Integration

### Table Provider Updates
- Added new_for_write() constructors
- Implemented insert_into() for TableProvider trait
- SQL INSERT OVERWRITE syntax support

### Dependencies
- Added serde, serde_json for metadata serialization
- Updated noodles-bgzf to git version (consistency)
- Added noodles-core for Position types

## Documentation

### Updated Files
- CLAUDE.md: Write support section, metadata schema conventions
- BAM README.md: Write examples, format conversion guide
- CRAM README.md: Reference sequence requirements, write examples
- WRITE_SUPPORT_SUMMARY.md: Complete implementation documentation

### Examples
- write_bam.rs: Filter, format conversion, SAM/BAM writing
- write_cram.rs: CRAM with/without reference, BAM to CRAM conversion

## Testing
- BAM: 14 tests passing (unit + integration)
- CRAM: 12 tests passing (unit + integration)
- Total: 26 tests covering compression, serialization, headers, round-trip

## Usage
```sql
-- Write filtered BAM
INSERT OVERWRITE output SELECT * FROM input WHERE mapping_quality >= 30;

-- Convert SAM to BAM
INSERT OVERWRITE bam_output SELECT * FROM sam_input;

-- Convert BAM to CRAM
INSERT OVERWRITE cram_output SELECT * FROM bam_input;
```

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Address clippy warnings in serializers

- Remove unnecessary reference operators (&&T -> &T)
- Replace len() > 0 with !is_empty() for clarity

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Correct example code and dependencies

- Fix schema conversion: use df.schema().inner().clone() instead of .into()
- Add missing thread_num parameter to BamTableProvider::new() calls
- Add datafusion-bio-format-bam as dev-dependency for CRAM examples
- All examples now compile and pass clippy checks

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: CRAM uses BAM metadata keys for common fields

Per CLAUDE.md and review comments, CRAM should reuse BAM metadata keys
for common fields to maintain consistency across alignment formats.

Changes:
- Move BAM_FILE_FORMAT_VERSION_KEY and BAM_COMMENTS_KEY to core/metadata.rs
- Export all BAM header metadata keys from core
- Update CRAM header_builder to import and use BAM constants
- Reserve bio.cram.* prefix only for CRAM-specific keys:
  * bio.cram.file_format_version (overrides BAM version)
  * bio.cram.reference_path (CRAM-specific)
  * bio.cram.reference_md5 (CRAM-specific)
- Update BAM header_builder to import from core
- Update CLAUDE.md to clarify CRAM metadata key usage
- Update tests to use BAM constants

All tests passing (26/26), clippy clean.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Preserve sort order, read group, and program metadata in headers

Addresses Codex review issues by ensuring header metadata is properly
preserved during BAM/CRAM write operations:

- Sort order (SO) now written to @hd header line using noodles tag constants
- Read group fields (SM, PL, LB, DS) now properly populated in @rg lines
- Program fields (PN, VN, CL) now properly populated in @pg lines

The fix uses noodles' public tag constants (SORT_ORDER, SAMPLE, PLATFORM,
LIBRARY, DESCRIPTION, NAME, VERSION, COMMAND_LINE) via other_fields_mut()
instead of attempting to construct private Other types.

This ensures round-trip metadata fidelity when reading and writing
BAM/SAM/CRAM files.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: Add comprehensive tag write integration tests

Add integration tests to verify that BAM alignment tags are properly
preserved during write operations:

- test_tags_round_trip: Verifies integer (NM, AS) and string (MD) tags
  round-trip correctly through write->read cycle
- test_write_without_tags: Ensures writing without tags works correctly

The tests confirm that:
1. Tag metadata is preserved in Arrow schema
2. Tags are correctly serialized to BAM records
3. Tags can be read back and match original values
4. Optional tag fields work correctly

Float tag test disabled temporarily pending schema resolution issue.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* test: Add comprehensive tests for all BAM tag data types

Extend tag write tests to cover all SAM tag types:

✅ Working (verified round-trip):
- Integer tags (i): NM, AS
- String tags (Z): MD

⚠️ Written correctly but reader schema issues:
- Float tags (f): XS
- Character tags (A): XT
- Integer array tags (B): ZB
- Byte array tags (B): ZC

The serializer correctly handles all tag types:
- Extracts tag metadata from Arrow schema
- Converts Arrow values to appropriate SAM tag value types
- Inserts tags into BAM record data

Tests marked as ignored for types with reader schema determination
issues. The tags are written correctly to BAM files (can be verified
with samtools), but the reader needs schema fixes for these types.

This confirms the tag write functionality is complete and correct
for all SAM tag types.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Resolve BAM reader schema determination for float, character, and array tags

This commit fixes issue #53 where the BAM reader failed to read back float,
character, and array tags with "Expected Float32Builder" and similar errors.

Key changes:
- Add dynamic schema inference via try_new_with_inferred_schema() method
  that samples BAM records to discover actual tag types
- Fix set_tag_builders() to use inferred schema instead of static registry,
  ensuring builders match actual data types
- Correct character tag serialization to use TagValue::Character instead of
  integer values
- Fix character value reading to convert u8 to char properly
- Update tests to handle Int32 normalization for integer arrays

All 6 tag round-trip tests now pass, including:
- Float tags (f): XS alignment scores
- Character tags (A): XT read types
- Integer array tags (B): Int32 arrays
- Byte array tags (B): Normalized to Int32

Resolves #53

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Apply same tag schema fixes to CRAM reader

Apply the same schema determination and character tag fixes from BAM
to the CRAM reader to resolve null tag values:

- Update set_tag_builders() to use inferred schema instead of static
  registry, ensuring builders match actual data types
- Fix character tag serialization to use TagValue::Character
- Fix character value reading to convert u8 to char properly

This resolves the issue where CRAM reader returned null for MD and
other tags even though they were present in the file.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* debug: Add logging to trace CRAM tag reading

* feat: Add MD/NM tag calculation for CRAM files (partial fix for #54)

Implements calculated tag support for MD (mismatch descriptor) and NM
(edit distance) tags that CRAM files typically omit since they can be
derived from alignment features and reference sequences.

## Changes

### Core Module
- Add calculated_tags.rs with functions to calculate MD/NM from CIGAR + reference
- Follows SAM specification for edit distance and mismatch calculation
- Supports calculation with or without reference sequence

### CRAM Reader
- Update load_tags() to detect and calculate missing MD/NM tags
- Add reference_seq parameter for tag calculation
- Add debug logging for tag resolution tracing

### Diagnostics
- Add debug_tags example to inspect tag availability
- Helps diagnose whether tags are stored or need calculation

### Documentation
- Add ISSUE_54_ANALYSIS.md with comprehensive root cause analysis
- Documents CRAM's on-the-fly MD/NM calculation behavior
- Includes references to SAM spec and htslib documentation

## Status

✅ Tag calculation logic implemented and tested
✅ CRAM reader updated with fallback mechanism
⚠️ Reference sequence integration pending (load_tags called with None)

## Next Steps

1. Implement reference region fetching in CramReader
2. Wire up reference sequences to load_tags() calls
3. Add integration tests with CRAM files

## References

- SAM spec: https://samtools.github.io/hts-specs/SAMtags.pdf
- samtools-calmd: http://www.htslib.org/doc/samtools-calmd.html
- htsjdk issue: samtools/htsjdk#1350

Related to #54

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* docs: Remove ISSUE_54_ANALYSIS.md (will update GitHub issue instead)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: Complete reference sequence integration for MD/NM tag calculation

Finishes the implementation of MD and NM tag calculation for CRAM files
by wiring up reference sequence fetching and passing to tag loaders.

## Changes

### Storage Layer (storage.rs)
- Add load_all_references() to pre-load reference sequences into memory
- Add fetch_reference_region() for on-demand region fetching
- Implement helper functions using noodles FASTA repository API

### Physical Execution (physical_exec.rs)
- Load all reference sequences before processing records
- Extract appropriate reference region for each alignment
- Pass reference region to load_tags() for MD/NM calculation
- Add debug logging for reference loading status

### Tests (calculated_tags.rs)
- Fix test code to use RecordBuf builder pattern
- Add noodles-core as dev-dependency for Position type
- All 8 tests passing (NM/MD calculation with matches, mismatches, indels)

## How It Works

1. **Reference Loading**: When CramReader is created with external reference,
   all reference sequences are loaded into a Vec<Vec<u8>> indexed by seq_id

2. **Per-Record Extraction**: For each record, we extract the reference
   region using alignment_start and alignment_end positions

3. **Tag Calculation**: load_tags() receives the reference region and
   automatically calculates MD/NM if they're not stored in record.data()

## Performance Considerations

- Reference sequences loaded once at stream initialization
- Region extraction is simple array slicing (very fast)
- Only applies to external references (not embedded CRAM references)
- Falls back to NULL if reference unavailable

## Limitations

- Embedded references not yet supported (would need CRAM container decoding)
- Entire reference sequences loaded into memory (fine for human genome ~3GB)
- For very large references, could optimize with caching/windowing

Completes #54

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* feat: Add automatic tag discovery for CRAM files (fixes #54)

This commit adds the `try_new_with_inferred_schema()` method to
CramTableProvider, which samples CRAM files to automatically discover
and include available tags in the schema. This fixes the issue where
tags like MD/NM returned NULL because they weren't registered in the
schema, even when present in the file.

Key changes:
- Add `try_new_with_inferred_schema()` async method for automatic tag
  discovery by sampling file records
- Extract `infer_type_from_cram_value()` as a standalone helper function
  for reuse across methods
- Auto-include MD/NM tags when reference is available, enabling
  calculation even if not stored in file
- Update examples to use new schema inference method
- Add comprehensive documentation showing both explicit and inferred
  schema approaches
- Add test_tag_discovery.rs example demonstrating the fix

The root cause was that tags must be explicitly requested via the
`tag_fields` parameter, and all examples passed `None`, causing tags
to never be registered in the schema. This new method mirrors BAM's
implementation and provides the expected behavior where tags present
in files are automatically readable.

Verified with polars-bio test.cram file - tags now correctly return
values matching samtools output instead of NULL.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Remove unnecessary usize casts in calculated_tags

The op.len() method returns usize already, so the explicit cast is
redundant and triggers clippy warnings in CI.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* chore: Remove broken debug_tags example

This old debugging example has API incompatibilities and is superseded
by test_tag_discovery.rs which provides better tag inspection
functionality with the current noodles API.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

* fix: Honor tag_fields parameter in new_for_write

When tag_fields are explicitly passed to new_for_write() but those
fields lack metadata, insert_into() was silently ignoring them and
only using schema metadata. This caused data loss when constructing
schemas programmatically or via SQL.

Now merge explicitly provided tag_fields with metadata-derived tags
to ensure all requested tags are written to output files.

Fixes chatgpt-codex-connector P2 issues #3 and #4.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
mwiewior added a commit that referenced this pull request Feb 10, 2026
- Fix memory leak: replace Box::leak(&'static str) with owned String
  in sequential scan's ColumnValue enum (Claude #1, Codex #4)
- Fix negative int cast: add >= 0 guards to Int32/Int64 arms in
  scalar_to_u64 to prevent wrapping to u64::MAX (Claude #2)
- Fix contig_lengths ordering: build in contig_names (tabix index)
  order instead of header.chromsizes order (Claude #3)
- Fix GZIP handling: add GzDecoder variant to PairsLocalReader so
  GZIP-compressed files are decompressed instead of read as plain
  text (Codex #5)
- Fail early for remote paths: check storage type before attempting
  local File::open, return clear NotImplemented error (Codex #6)

Co-Authored-By: Claude Opus 4.6 <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.

2 participants