<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Data on FastDataScience.eu</title>
    <link>https://fastdatascience.eu/tags/data/</link>
    <description>FastDataScience.eu (Data)</description>
    <generator>Hugo -- gohugo.io</generator>
    <copyright>en-us</copyright>
    <lastBuildDate>Sat, 12 Sep 2026 00:00:00 +0000</lastBuildDate>
    
    <atom:link href="https://fastdatascience.eu/tags/data/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Fast queries on Parquet data in Rust</title>
      <link>https://fastdatascience.eu/post/2026-09-12-datafusion/</link>
      <pubDate>Sat, 12 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://fastdatascience.eu/post/2026-09-12-datafusion/</guid>
      <description>&lt;p&gt;Several projects in banking and consumer products have made use of data stored in data lakes in
&lt;a href=&#34;https://en.wikipedia.org/wiki/Apache_Parquet&#34;&gt;Parquet&lt;/a&gt;,
a compact and efficient column-oriented format.
Python with &lt;a href=&#34;https://pandas.pydata.org/&#34;&gt;Pandas&lt;/a&gt; is a feasible
but very inefficient choice for this, and I have typically used
&lt;a href=&#34;https://spark.apache.org/&#34;&gt;Spark&lt;/a&gt; here. Recently revisting Rust for
long-lived MCP servers, I have been exploring
&lt;a href=&#34;https://datafusion.apache.org/index.html&#34;&gt;DataFusion&lt;/a&gt; for direct
queries on Parquet data from Rust, and am very impressed.&lt;/p&gt;
&lt;p&gt;DataFusion describes itself as &lt;em&gt;an extensible query engine written in Rust
that uses Apache Arrow as its in-memory format&lt;/em&gt;. The web site explains that
&lt;em&gt;Out of the box, DataFusion offers SQL and Dataframe APIs, excellent performance,
built-in support for CSV, Parquet, JSON, and Avro, extensive customization, and
a great community.&lt;/em&gt; It is basically a library to access these data formats
and query them, either via chained declarative function calls, or via strings
with SQL queries.&lt;/p&gt;
&lt;p&gt;To test it, I used two years&amp;rsquo; worth of the
&lt;a href=&#34;https://www.nyc.gov/site/tlc/about/tlc-trip-record-data.page&#34;&gt;New York City taxi rides&lt;/a&gt;
data set, which is made available in Parquet format.  Download any number of
months of the Parquet files, and put them into a directory called &amp;lsquo;data&amp;rsquo;.  I
wanted to test not just the ability to read and access the files, but to
perform queries using a declarative syntax such as SQL, since that would be the
enabler of MCP access to the data.&lt;/p&gt;
&lt;p&gt;You can DataFusion into your projects by adding the following dependencies to
your Cargo.toml:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;[dependencies]
datafusion = &amp;#34;55.0.0&amp;#34;
tokio = { version = &amp;#34;1.0&amp;#34;, features = [&amp;#34;rt-multi-thread&amp;#34;] }
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The functions can be imported in your Rust code as follows:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;use datafusion::arrow::array::RecordBatch;
use datafusion::arrow::util::pretty::pretty_format_batches;
use datafusion::error::Result;
use datafusion::prelude::*;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then, the following code will register the tables, including reading the schema
and other metadata (but not yet the data itself):&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Create a context and register the table, assumes data is in &amp;#39;data&amp;#39; subdirectory of parent
let ctx = SessionContext::new();
ctx.register_parquet(&amp;#34;rides&amp;#34;, &amp;#34;../data&amp;#34;, ParquetReadOptions::new())
    .await?;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;To run the query, execute the sql() function on the context, and either collect()
or iterate the resulting rows:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Create a plan to run a SQL query
let df = ctx.sql(q).await?;

// Execute and collect the results
let batches = df.collect().await?;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The above code collects the entire result, which makes sense for an MCP
server, which returns the result of a request. If the results are very large,
MCP allows &lt;a href=&#34;https://modelcontextprotocol.io/specification/draft/server/utilities/pagination&#34;&gt;pagination&lt;/a&gt;.
And within Rust, you can also iterate row-by-row for programmatic
aggregation or other processing.&lt;/p&gt;
&lt;p&gt;Putting it together, here is a Rust command-line program that takes a query
as the argument (must be in quotes), runs the query, and shows the result:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;// Execute query on command line against Parquet files in the data directory,
// which is assumed to be under the parent directory.
//
// Sample usage:  cargo run &amp;#34;select avg(fare_amount) from rides&amp;#34;
//
// Sample output:
// Query: select avg(fare_amount) from rides
// +------------------------+
// | avg(rides.fare_amount) |
// +------------------------+
// | 19.219927893175566     |
// +------------------------+

use datafusion::arrow::array::RecordBatch;
use datafusion::arrow::util::pretty::pretty_format_batches;
use datafusion::error::Result;
use datafusion::prelude::*;

#[tokio::main]
async fn main() -&amp;gt; Result&amp;lt;()&amp;gt; {
    // Get query from the command line
    let args: Vec&amp;lt;_&amp;gt; = std::env::args().collect();
    if args.len() != 2 {
        println!(&amp;#34;Missing query or extra arguments&amp;#34;);
        std::process::exit(1);
    }
    let q = args[1].as_str();
    println!(&amp;#34;Query: {}&amp;#34;, q);

    // Execute query and show result
    let batches = query(&amp;amp;q).await?;
    println!(&amp;#34;{}&amp;#34;, pretty_format_batches(&amp;amp;batches)?);
    Ok(())
}

// Execute a query, returning the resulting record batches
async fn query(q: &amp;amp;str) -&amp;gt; Result&amp;lt;Vec&amp;lt;RecordBatch&amp;gt;&amp;gt; {
    // Register the table
    let ctx = SessionContext::new();
    ctx.register_parquet(&amp;#34;rides&amp;#34;, &amp;#34;../data&amp;#34;, ParquetReadOptions::new())
        .await?;

    // Create a plan to run a SQL query
    let df = ctx.sql(q).await?;

    // Execute and collect the results
    let batches = df.collect().await?;

    Ok(batches)
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In the next post, we&amp;rsquo;ll put this into an MCP server, to make fast Parquet data
lake queries available as a tool to an LLM.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
