A lightweight dbt-like CLI tool built in Rust for SQL templating, compilation, and execution against DuckDB.
For a deep dive into what Feather-Flow is, why it works the way it does, and how the schema validation framework and static analysis engine fit together, see HOW_FEATHERFLOW_WORKS.md.
- SQL Templating: Jinja-style templating with
config()andvar()functions - Custom Macros: Reusable SQL macros loaded from
macro_pathsdirectories - AST-based Dependencies: Automatically extracts dependencies from SQL using
sqlparser-rs- no need forref()orsource()functions - Dependency-aware Execution: Builds a DAG and executes models in topological order
- Schema Testing: Built-in support for 10 test types (
unique,not_null,positive,non_negative,accepted_values,min_value,max_value,regex,relationship,custom) with sample failing rows - Source Definitions: Document and test external data sources
- Documentation Generation: Generate markdown, JSON, or HTML docs from schema files, with an interactive docs server
- DuckDB Backend: In-memory or file-based DuckDB database execution
- Static Analysis: DataFusion-based SQL analysis passes for type checking and best practices
- SQL Formatting: Built-in SQL formatter with Jinja support
- Column Lineage: Trace column-level lineage across models
curl -fsSL https://raw.githubusercontent.com/datastx/Feather-Flow/main/install.sh | bashThis detects your OS and architecture, downloads the correct binary from the latest GitHub Release, verifies the SHA256 checksum, and installs to ~/.local/bin.
Options:
# Install a specific version
curl -fsSL https://raw.githubusercontent.com/datastx/Feather-Flow/main/install.sh | FF_VERSION=0.1.0 bash
# Install to a custom directory
curl -fsSL https://raw.githubusercontent.com/datastx/Feather-Flow/main/install.sh | INSTALL_DIR=/usr/local/bin bashPre-built binaries are available on the Releases page:
| Platform | Artifact |
|---|---|
| Linux x86_64 | ff-x86_64-linux-gnu |
| macOS x86_64 | ff-x86_64-apple-darwin |
| macOS ARM (Apple Silicon) | ff-aarch64-apple-darwin |
# Example: download latest for Linux x86_64
curl -fsSL https://github.com/datastx/Feather-Flow/releases/latest/download/ff-x86_64-linux-gnu -o ff
chmod +x ff
sudo mv ff /usr/local/bin/docker pull ghcr.io/datastx/feather-flow:latest
# Run any ff command
docker run --rm -v "$(pwd)":/workspace -w /workspace ghcr.io/datastx/feather-flow dt compile
docker run --rm -v "$(pwd)":/workspace -w /workspace ghcr.io/datastx/feather-flow run
docker run --rm -v "$(pwd)":/workspace -w /workspace ghcr.io/datastx/feather-flow run --mode testgit clone https://github.com/datastx/Feather-Flow.git
cd Feather-Flow
make build-release
# Binary is at target/release/ff
# Or install directly
cargo install --path crates/ff-cli- Create a project directory with a
featherflow.ymlconfiguration:
name: my_project
version: "1.0.0"
database:
type: duckdb
path: ":memory:"
node_paths:
- nodes
macro_paths:
- macros
vars:
env: dev
start_date: "2024-01-01"- Create SQL models in the
nodes/directory. Each node lives in its own directory with a.sqlfile and a.ymlschema file:
-- nodes/stg_orders/stg_orders.sql
{{ config(materialized='view', schema='staging') }}
SELECT
id AS order_id,
user_id AS customer_id,
created_at AS order_date,
amount,
status
FROM raw_orders
WHERE created_at >= '{{ var("start_date") }}'- Create a schema file alongside your model (1:1 convention):
# nodes/stg_orders/stg_orders.yml
version: 1
kind: sql
description: "Staged orders from raw source"
owner: data-team
tags:
- staging
- orders
columns:
- name: order_id
tests:
- unique
- not_null
- name: customer_id
tests:
- not_null
- name: order_date
- name: amount
- name: status- Run Featherflow commands:
# Load seed data
ff dt deploy seeds
# Compile models (renders Jinja, extracts dependencies, builds manifest)
ff dt compile
# List models and sources
ff dt ls
# Execute models in dependency order
ff run
# Run schema tests
ff run --mode test
# Validate project without execution
ff dt compile --parse-only
# Generate documentation
ff dt docsFeatherflow commands are organized into runtime commands (top-level) and developer tooling (ff dt):
Executes models, runs tests, or both depending on the --mode flag.
ff run [--mode <MODE>] [--nodes <NODES>] [--full-refresh] [--fail-fast]Run modes:
build(default) - Execute models then run their tests in DAG ordermodels- Execute models onlytest- Run tests only against existing tables
Node selectors:
model_name- Run a specific model+model_name- Run model and all ancestorsmodel_name+- Run model and all descendants+model_name+- Run model, ancestors, and descendantstag:X- Run models with a specific tagpath:X- Run models in a specific path
Execute a standalone SQL macro.
ff run-macro <MACRO_NAME> [--args <JSON>]Compiles SQL models by rendering Jinja templates, extracting dependencies, and generating a manifest.
ff dt compile [--nodes <NODES>] [--parse-only] [--strict] [--output <FORMAT>]Use --parse-only to validate without writing output files. Use --strict to treat warnings as errors.
Lists models and sources with their dependencies and materialization settings.
ff dt ls [--output <FORMAT>] [--nodes <NODES>] [--resource-type <TYPE>]Output formats: table (default), json, tree, path
Resource types: model, source, seed, test, function
Deploy seeds or functions to the database.
# Load CSV seed files
ff dt deploy seeds [--seeds <NAMES>] [--full-refresh]
# Deploy user-defined functions
ff dt deploy functions deploy [--functions <NAMES>]
# List, validate, show, or drop functions
ff dt deploy functions list|validate|show|dropGenerates documentation from schema files.
ff dt docs [--output <PATH>] [--format <FORMAT>] [--nodes <NODES>]
# Launch interactive documentation server
ff dt docs serve [--port <PORT>] [--no-browser] [--static-export <DIR>]Trace column-level lineage across models.
ff dt lineage --node <NODE> --column <COLUMN> [--direction <DIR>] [--output <FORMAT>]Run static analysis passes on SQL models.
ff dt analyze [--nodes <NODES>] [--output <FORMAT>] [--severity <LEVEL>]
# Query the metadata database
ff dt analyze query <SQL> [--json]
# Export metadata as JSON
ff dt analyze export [--output <FILE>]
# List metadata tables
ff dt analyze tablesFormat SQL source files.
ff dt fmt [<PATHS>] [--nodes <NODES>] [--check] [--diff]Initialize a new Featherflow project.
ff dt init [--name <NAME>] [--database_path <PATH>]Remove generated artifacts.
ff dt clean [--dry-run]--project-dir, -p <DIR>: Project directory (default: current directory)--config, -c <FILE>: Config file path--target, -t <TARGET>: Override target (database connection)--verbose, -v: Enable verbose output
name: project_name # Project name
version: "1.0.0" # Project version
database:
type: duckdb # Database type (duckdb, snowflake)
path: ":memory:" # Database path (":memory:" for in-memory)
name: main # Logical database name
dialect: duckdb # SQL dialect (duckdb, snowflake)
materialization: view # Default materialization (view, table, incremental, ephemeral)
schema: main # Default schema
node_paths: # Directories containing all node types (models, seeds, functions, sources)
- nodes
macro_paths: # Directories containing macro files
- macros
target_path: target # Output directory for compiled files
on_run_start: # SQL executed before each run
- "CREATE TABLE IF NOT EXISTS audit (id INTEGER)"
on_run_end: # SQL executed after each run
- "INSERT INTO audit (id) VALUES (1)"
vars: # Variables accessible via var()
env: dev
start_date: "2024-01-01"Use the config() function in your SQL models:
{{ config(materialized='table', schema='staging') }}
SELECT * FROM raw_dataSupported config options:
materialized:'view','table','incremental', or'ephemeral'schema: Target schema name
Access variables with var():
SELECT * FROM {{ var('schema') }}.users
WHERE env = '{{ var('env', 'prod') }}'Create reusable macros in macros/:
-- macros/date_utils.sql
{% macro date_trunc(date_col, granularity) %}
DATE_TRUNC('{{ granularity }}', {{ date_col }})
{% endmacro %}Use in models:
{% from "date_utils.sql" import date_trunc %}
SELECT
{{ date_trunc('order_date', 'month') }} AS order_month,
SUM(amount) AS total
FROM orders
GROUP BY 1my_project/
├── featherflow.yml
├── nodes/
│ ├── stg_orders/
│ │ ├── stg_orders.sql # SQL model
│ │ └── stg_orders.yml # Schema + tests
│ ├── stg_customers/
│ │ ├── stg_customers.sql
│ │ └── stg_customers.yml
│ ├── fct_orders/
│ │ ├── fct_orders.sql
│ │ └── fct_orders.yml
│ └── raw_orders/
│ ├── raw_orders.csv # Seed data (kind: seed)
│ └── raw_orders.yml
├── macros/
│ └── date_utils.sql
└── target/
├── compiled/
├── manifest.json
└── run_results.json
# Build all crates
make build
# Run tests
make test
# Run end-to-end tests
make ci-e2e
# Run CI checks (format, clippy, test, doc)
make ciff-cli: CLI binary and commandsff-core: Core library (config, project, DAG, node types)ff-sql: SQL parsing and dependency extractionff-jinja: Jinja templating layer with macro supportff-db: Database abstraction and DuckDB backendff-test: Test generation and executionff-analysis: DataFusion-based static SQL analysisff-meta: Metadata database (DuckDB) for schema, population, and rules
MIT