microsoft / microsoft/graphrag

[Bug]: Table provider list() removes interior CSV/Parquet extensions

Open Beginner friendly
#2,538 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
36k
Forks
3.8k
Avg merge
20h 40m
Merged PRs (30d)
4

Description

Do you need to file an issue?
  • I have searched the existing open and closed issues and pull requests; I found no report or implementation of this behavior.
  • Not applicable: this direct graphrag-storage/MemoryStorage reproduction does not invoke a model provider or model call.
  • I believe this is a reproducible bug, not a usage question.
Describe the bug

CSVTableProvider.list() and ParquetTableProvider.list() do not preserve table names that contain the provider extension anywhere before the final generated extension.

For example, writing the CSV table name run.csv.2026 creates the expected storage key run.csv.2026.csv, but list() returns run.2026. The returned name cannot be used with has() or read_dataframe() to address the table that was written.

This happens because both implementations use global str.replace() calls when removing the generated file extension:

file.replace(".csv", "")
file.replace(".parquet", "")

The same public round-trip invariant is affected in both providers.

Steps to reproduce

On current main at f40e9a26ce62ba0b3fef8837d24aafdcc6e6c704:

import asyncio

import pandas as pd
from graphrag_storage import StorageConfig, StorageType, create_storage
from graphrag_storage.tables.csv_table_provider import CSVTableProvider


async def main() -> None:
    storage = create_storage(StorageConfig(type=StorageType.Memory))
    provider = CSVTableProvider(storage=storage)
    original = "run.csv.2026"

    await provider.write_dataframe(original, pd.DataFrame({"value": [1]}))
    listed = provider.list()

    print(listed)
    print(await provider.has(original))
    print(await provider.has(listed[0]))


asyncio.run(main())

Actual output:

['run.2026']
True
False

An equivalent Parquet storage key such as run.parquet.2026.parquet is returned as run.2026 for the same reason.

Expected behavior

list() should remove only the final provider-generated extension and return the original logical table name. In the example above, it should return run.csv.2026, and has(provider.list()[0]) should be True.

I can submit a focused patch that preserves interior extension text in both providers and adds a regression to each provider's unit tests if that scope is welcome.

GraphRAG config used

Not applicable. This reproduces directly against graphrag-storage with MemoryStorage; no model or GraphRAG indexing configuration is involved.

Logs and screenshots

No additional logs or screenshots are needed; the output above is deterministic.

Additional information
  • GraphRAG Version: current main (f40e9a26ce62ba0b3fef8837d24aafdcc6e6c704)
  • Operating system: macOS 26.6.2 (arm64)
  • Python version: 3.13.14
  • Existing focused CSV provider suite: 7 passed
  • Existing focused Parquet provider suite with locked pyarrow==25.0.1: 6 passed
  • AI assistance disclosure: This report was prepared with AI assistance. The CSV and Parquet reproductions, outputs, source locations, and issue/PR overlap above were checked on the stated revision.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with CSVTableProvider.list() and ParquetTableProvider.list(), then run the focused provider test suites described in the issue using MemoryStorage. Add regression coverage for names containing interior .csv or .parquet text; done means list() returns the original logical name and has(provider.list()[0]) remains true.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
databases
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
86/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.