Skip to main content

Crate jetdb

Crate jetdb 

Source
Expand description

A pure Rust library for reading Microsoft Access database files (.mdb / .accdb). No ODBC drivers or C libraries required — read Access databases directly on macOS, Linux, or any platform Rust supports. Covers Access 97 (Jet3) through Access 2019 (ACE17).

§Quick Start

[dependencies]
jetdb = "0.2"
use jetdb::{PageReader, read_catalog};

fn main() -> Result<(), jetdb::FileError> {
    let mut reader = PageReader::open("database.mdb")?;
    let catalog = read_catalog(&mut reader)?;
    for entry in &catalog {
        println!("{}", entry.name);
    }
    Ok(())
}

§Data Flow

The jetdb API mirrors the page-based internal structure of Access databases. To read data, call functions in the following order:

PageReader::open → read_catalog → read_table_def → read_table_rows → Value
  1. PageReader::open opens the database file. It automatically detects the engine version from the file header and prepares RC4 decryption if needed. For password-protected .accdb files, use PageReader::open_with_password instead.
  2. read_catalog reads the system catalog (MSysObjects) and returns a list of database objects as a vector of CatalogEntry.
  3. read_table_def parses the table definition page (TDEF) and returns a TableDef containing column and index information.
  4. read_table_rows scans the data pages and returns each row’s values as Value enums.

§Reading Table Data

This is the most common use case. Here is a complete example from opening a file to retrieving row data:

use jetdb::{PageReader, read_catalog, read_table_def, read_table_rows, Value};

fn main() -> Result<(), jetdb::FileError> {
    let mut reader = PageReader::open("database.mdb")?;

    // Find a table in the catalog
    let catalog = read_catalog(&mut reader)?;
    let entry = catalog.iter()
        .find(|e| e.name == "Customers")
        .expect("table not found");

    // Read the table definition
    let table_def = read_table_def(&mut reader, &entry.name, entry.table_page)?;

    // Print column names
    for col in &table_def.columns {
        print!("{}\t", col.name);
    }
    println!();

    // Read row data
    let result = read_table_rows(&mut reader, &table_def)?;
    for row in &result.rows {
        for value in row {
            match value {
                Value::Text(s) => print!("{s}\t"),
                Value::Long(n) => print!("{n}\t"),
                Value::Double(f) => print!("{f}\t"),
                Value::Null => print!("(null)\t"),
                other => print!("{other:?}\t"),
            }
        }
        println!();
    }

    Ok(())
}

The skipped_rows field of ReadResult indicates how many rows were skipped due to read errors. Call warn_skipped(table) to emit a log::warn! message when any rows were skipped. Internal metadata readers (catalog, queries, relationships, properties, VBA) call this automatically.

§The Value Type

The Value enum maps to Access data types:

Value VariantAccess TypeNotes
Null(NULL for any type)
Bool(bool)Yes/No
Byte(u8)Byte
Int(i16)Integer
Long(i32)Long Integer
BigInt(i64)Large NumberACE16+
Float(f32)Single
Double(f64)Double
Text(String)Text / MemoMemo for long text
Binary(Vec<u8>)Binary / OLE Object
Money(String)CurrencyFixed-point string (4 decimal places)
Numeric(String)DecimalVariable-scale string
Timestamp(f64)Date/TimeDays since 1899-12-30
Guid(String)Replication ID{XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} format

Money and Numeric are returned as strings to preserve precision. Timestamp is the raw OLE Date value (a floating-point day count from 1899-12-30).

§Other Features

§Listing Table Names

Use table_names when you only need the names of user-created tables. System tables and hidden tables are automatically excluded.

use jetdb::{PageReader, table_names};

fn main() -> Result<(), jetdb::FileError> {
    let mut reader = PageReader::open("database.mdb")?;
    let names = table_names(&mut reader)?;
    for name in &names {
        println!("{name}");
    }
    Ok(())
}

§Schema Information

TableDef contains column definitions (ColumnDef) and index definitions (IndexDef).

use jetdb::{PageReader, read_catalog, read_table_def};

fn main() -> Result<(), jetdb::FileError> {
    let mut reader = PageReader::open("database.mdb")?;
    let catalog = read_catalog(&mut reader)?;
    let entry = catalog.iter().find(|e| e.name == "Customers").unwrap();
    let table_def = read_table_def(&mut reader, &entry.name, entry.table_page)?;

    for col in &table_def.columns {
        println!("{}: {:?}", col.name, col.col_type);
    }
    for idx in &table_def.indexes {
        println!("INDEX {}: {:?}", idx.name, idx.columns);
    }
    Ok(())
}

§Saved Query SQL Recovery

Use read_queries to read saved query definitions and query_to_sql to reconstruct the SQL.

use jetdb::{PageReader, read_queries, query_to_sql};

fn main() -> Result<(), jetdb::FileError> {
    let mut reader = PageReader::open("database.mdb")?;
    let queries = read_queries(&mut reader)?;
    for qdef in &queries {
        println!("-- {} ({:?})", qdef.name, qdef.query_type);
        println!("{}", query_to_sql(qdef));
    }
    Ok(())
}

§Relationships

Use read_relationships to retrieve foreign key definitions between tables.

use jetdb::{PageReader, read_relationships};

fn main() -> Result<(), jetdb::FileError> {
    let mut reader = PageReader::open("database.mdb")?;
    let rels = read_relationships(&mut reader)?;
    for rel in &rels {
        println!("{}: {} -> {}", rel.name, rel.from_table, rel.to_table);
    }
    Ok(())
}

§VBA Source Code

Use read_vba_project to extract VBA module source code. For databases without VBA, an empty VbaProject is returned.

use jetdb::{PageReader, read_vba_project};

fn main() -> Result<(), jetdb::FileError> {
    let mut reader = PageReader::open("database.mdb")?;
    let project = read_vba_project(&mut reader)?;
    for module in &project.modules {
        println!("--- {} ({:?}) ---", module.name, module.module_type);
        println!("{}", module.source);
    }
    Ok(())
}

§DDL Generation

The ddl module generates DDL for SQLite, PostgreSQL, MySQL, or Access SQL from table definitions.

use jetdb::{PageReader, read_catalog, read_table_def, read_relationships};
use jetdb::ddl::{generate_ddl, Sqlite};

fn main() -> Result<(), jetdb::FileError> {
    let mut reader = PageReader::open("database.mdb")?;
    let catalog = read_catalog(&mut reader)?;

    let mut tables = Vec::new();
    for entry in &catalog {
        if entry.object_type == jetdb::ObjectType::Table
            && !entry.name.starts_with("MSys")
        {
            tables.push(read_table_def(&mut reader, &entry.name, entry.table_page)?);
        }
    }
    let rels = read_relationships(&mut reader)?;
    let sql = generate_ddl(&Sqlite, &tables, &rels, true, true);
    println!("{sql}");
    Ok(())
}

Available dialects: ddl::Sqlite, ddl::Postgres, ddl::Mysql, ddl::Access.

§Password-Protected Databases

Use PageReader::open_with_password to open .accdb files encrypted with Agile Encryption (Access 2007+).

use jetdb::{PageReader, table_names};

fn main() -> Result<(), jetdb::FileError> {
    let mut reader = PageReader::open_with_password("protected.accdb", Some("secret"))?;
    let names = table_names(&mut reader)?;
    for name in &names {
        println!("{name}");
    }
    Ok(())
}

For non-encrypted files, open_with_password works the same as open (the password is ignored).

§Error Handling

All public functions return Result<T, FileError>. FileError is an enum with variants for I/O errors, format errors, and missing objects, and can be propagated with the ? operator.

§Supported Versions

EngineAccess VersionFile Format
Jet3Access 97.mdb
Jet4Access 2000/2003.mdb
ACE12Access 2007.accdb
ACE14Access 2010.accdb
ACE15Access 2013.accdb
ACE16Access 2016.accdb
ACE17Access 2019.accdb

§Limitations

  • Read-only (no write support)
  • No index-based lookups (full table scan only)
  • read_table_rows loads all rows into memory; be mindful of memory usage with very large tables
  • Password-protected .accdb files require PageReader::open_with_password (Agile Encryption)

Re-exports§

pub use catalog::read_catalog;
pub use catalog::table_names;
pub use catalog::CatalogEntry;
pub use file::find_row;
pub use file::DbHeader;
pub use file::FileError;
pub use file::PageReader;
pub use format::catalog_flags;
pub use format::column_flags;
pub use format::index_flags;
pub use format::index_type;
pub use format::ColumnType;
pub use format::FormatError;
pub use format::JetFormat;
pub use format::JetVersion;
pub use format::ObjectType;
pub use format::PageType;
pub use format::JET3;
pub use format::JET4;
pub use relationship::read_relationships;
pub use relationship::relationship_flags;
pub use relationship::Relationship;
pub use relationship::RelationshipColumn;
pub use table::is_replication_column;
pub use table::read_table_def;
pub use table::ColumnDef;
pub use table::ForeignKeyReference;
pub use table::IndexColumn;
pub use table::IndexColumnOrder;
pub use table::IndexDef;
pub use table::TableDef;
pub use data::read_table_rows;
pub use data::ReadResult;
pub use data::Value;
pub use prop::read_object_properties;
pub use prop::ObjectProperties;
pub use prop::PropMapType;
pub use prop::Property;
pub use prop::PropertyMap;
pub use query::query_to_sql;
pub use query::read_queries;
pub use query::QueryDef;
pub use query::QueryType;
pub use vba::read_vba_project;
pub use vba::VbaModule;
pub use vba::VbaModuleType;
pub use vba::VbaProject;

Modules§

catalog
MSysObjects system catalog reading and table discovery.
data
Data row reading and value extraction from table pages.
ddl
DDL (CREATE TABLE / INDEX / FOREIGN KEY) generation for multiple SQL dialects.
encoding
Text decoding: Latin-1 (Jet3), UTF-16LE (Jet4+), and Unicode compression.
file
Page-level I/O, RC4 decryption, and database header parsing.
format
Jet/ACE database format constants, version detection, and layout definitions.
map
Page usage map (bitmap) traversal for locating table data pages.
money
Money and Numeric fixed-point value formatting.
prop
Object property reading from MSysObjects.LvProp blobs.
query
Stored query reading and SQL reconstruction from MSysQueries.
relationship
Table relationship (foreign key) reading from MSysRelationships.
table
Table definition (TDEF page) parsing: columns, indexes, and data page lists.
timestamp
vba
VBA project and module source code extraction.