DuckDB-Manifold Extension

December 31, 2025 ยท View on GitHub

A DuckDB extension for querying ManifoldDB graph databases directly via SQL.

What It Does

Query ManifoldDB's redb storage directly from DuckDB - no data export, no ETL. Point DuckDB at your .redb file and run SQL.

Installation

Building from Source

# Clone with submodules
git clone --recurse-submodules https://github.com/tomWhiting/duckdb-manifold.git
cd duckdb-manifold

# Configure (sets up Python venv with DuckDB)
make configure

# Build
make debug    # or: make release

Dependencies

  • Rust toolchain
  • Python 3
  • Make
  • Git

Usage

import duckdb

conn = duckdb.connect(config={'allow_unsigned_extensions': True})
conn.execute("LOAD 'build/debug/duckdb_manifold.duckdb_extension'")

# Query entities
result = conn.execute("SELECT * FROM manifold_entities('/path/to/database.redb')")
for row in result.fetchall():
    print(row)

Query Entities

SELECT * FROM manifold_entities('/path/to/database.redb');

Returns:

  • id - Entity ID (VARCHAR)
  • labels - JSON array of labels (VARCHAR)
  • prop_* - One column per discovered property

Query Edges

SELECT * FROM manifold_edges('/path/to/database.redb');

Returns:

  • id - Edge ID (VARCHAR)
  • source - Source entity ID (VARCHAR)
  • target - Target entity ID (VARCHAR)
  • edge_type - Relationship type (VARCHAR)
  • prop_* - One column per discovered property

Filter, Aggregate, Join

Full DuckDB SQL works:

-- Filter entities
SELECT prop_name, CAST(prop_age AS INTEGER) as age
FROM manifold_entities('/path/to/db.redb')
WHERE prop_age != '' AND CAST(prop_age AS INTEGER) > 25;

-- Aggregate edges
SELECT edge_type, COUNT(*) as count
FROM manifold_edges('/path/to/db.redb')
GROUP BY edge_type;

-- Join entities and edges
SELECT e1.prop_name as person, edge.edge_type, e2.prop_name as target
FROM manifold_entities('/path/to/db.redb') e1
JOIN manifold_edges('/path/to/db.redb') edge ON e1.id = edge.source
JOIN manifold_entities('/path/to/db.redb') e2 ON edge.target = e2.id;

How It Works

  • Dynamic schema discovery: Samples entities at bind time to discover property columns
  • Cursor-based streaming: Reads in batches of 1024 for efficiency
  • Shared engine cache: Multiple queries share the same database connection
  • All columns VARCHAR: DuckDB casts as needed in queries

Testing

# Run integration test
cargo run --bin integration_test

Target DuckDB Version

v1.4.3

License

MIT