Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,5 @@ max_line_length = 100
max_line_length = 150
trim_trailing_whitespace = false

[*.sh]
indent_size = 2

[*.{yml,yaml}]
indent_size = 2
2 changes: 1 addition & 1 deletion .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Install Zig
uses: goto-bus-stop/setup-zig@v2
with:
version: '0.15.2'
version: '0.16.0'

- name: Install System Dependencies
run: |
Expand Down
51 changes: 35 additions & 16 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,54 @@ on:
workflow_dispatch:
push:
branches:
- main
- develop
tags:
- 'v*'
paths-ignore:
- '**.md'
- 'docs/**'
pull_request:
branches:
- main
paths-ignore:
- '**.md'
- 'docs/**'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
tests:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
zig-url: https://ziglang.org/download/0.16.0/zig-x86_64-linux-0.16.0.tar.xz
zig-dir: zig-x86_64-linux-0.16.0
- os: macos-latest
zig-url: https://ziglang.org/download/0.16.0/zig-aarch64-macos-0.16.0.tar.xz
zig-dir: zig-aarch64-macos-0.16.0
- os: windows-latest
zig-url: https://ziglang.org/download/0.16.0/zig-x86_64-windows-0.16.0.zip
zig-dir: zig-x86_64-windows-0.16.0

steps:
- name: Checkout Repository
- name: Checkout repository
uses: actions/checkout@v4

- name: Install Dependencies
- name: Install Zig 0.16.0 (Unix)
if: runner.os != 'Windows'
run: |
curl -sSfL ${{ matrix.zig-url }} | tar -xJ
echo "$PWD/${{ matrix.zig-dir }}" >> "$GITHUB_PATH"

- name: Install Zig 0.16.0 (Windows)
if: runner.os == 'Windows'
shell: pwsh
run: |
sudo apt-get update
sudo apt-get install -y make
make install-deps
Invoke-WebRequest -Uri "${{ matrix.zig-url }}" -OutFile zig.zip
Expand-Archive zig.zip -DestinationPath .
echo "$PWD\${{ matrix.zig-dir }}" | Out-File -Append -FilePath $env:GITHUB_PATH

- name: Run Tests
run: make test
- name: Run tests
run: zig build test --summary all
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,6 @@ docs/api/
POST.md
site/
core.*
.claude/
.codex
zig-pkg/
157 changes: 157 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
# AGENTS.md

This file provides guidance to coding agents collaborating on this repository.

## Mission

Zodd is a small, embeddable [Datalog](https://en.wikipedia.org/wiki/Datalog) engine written in pure Zig.
It evaluates recursive rules over sets of tuples using semi-naive iteration, merge joins, and indexed extension primitives.
Zodd is designed to be embedded in Zig projects as a library.
Priorities, in order:

1. Correctness of relations, variables, joins, extensions, and fixed-point iteration.
2. Minimal public API for use as a library from other Zig projects.
3. Small dependency footprint and maintainable, well-tested code.
4. Cross-platform support (Linux, macOS, and Windows).

## Core Rules

- Use English for code, comments, docs, and tests.
- Prefer small, focused changes over large refactoring.
- Add comments only when they clarify non-obvious behavior.
- Do not add features, error handling, or abstractions beyond what is needed for the current task.
- Keep the dependency set small: do not add new Zig packages or C libraries without prior discussion.

## Writing Style

- Use Oxford commas in inline lists: "a, b, and c" not "a, b, c".
- Do not use em dashes. Restructure the sentence, or use a colon or semicolon instead.
- Avoid colorful adjectives and adverbs. Write "Datalog engine" not "blazing-fast Datalog engine", "merge join" not "efficient merge join".
- Use noun phrases for checklist items, not imperative verbs. Write "redundant index detection" not "detect redundant indexes".
- Headings in Markdown files must be in the title case: "Build from Source" not "Build from source". Minor words (a, an, the, and, but, or, for, in,
on, at, to, by, of, is, are, was, were, be) stay lowercase unless they are the first word.

## Repository Layout

- `src/lib.zig`: Public API entry point. Re-exports `Relation`, `Variable`, `Iteration`, `ExecutionContext`, join helpers, and extend primitives.
- `src/zodd/relation.zig`: Immutable `Relation` type (sorted, deduplicated tuples).
- `src/zodd/variable.zig`: Mutable `Variable` type for fixed-point iteration, plus the `gallop` search helper.
- `src/zodd/iteration.zig`: `Iteration` driver for semi-naive evaluation.
- `src/zodd/join.zig`: Merge-join algorithms (`joinHelper`, `joinInto`, `joinAnti`).
- `src/zodd/extend.zig`: Leaper-based extension primitives (`ExtendWith`, `FilterAnti`, `ExtendAnti`, `extendInto`).
- `src/zodd/index.zig`: Indexes for keyed lookups.
- `src/zodd/aggregate.zig`: Group-by and aggregation operations.
- `tests/`: Non-unit tests (`integration_tests.zig`, `regression_tests.zig`, `property_tests.zig`, `incremental_tests.zig`).
- `examples/`: Self-contained example programs (`e1_network_reachability.zig` through `e6_dependency_resolution.zig`) built as executables via
`build.zig`.
- `.github/workflows/`: CI workflows (`tests.yml` for unit and integration tests, `docs.yml` for API doc deployment).
- `build.zig` / `build.zig.zon`: Zig build configuration and package metadata.
- `Makefile`: GNU Make wrapper around `zig build` targets.
- `docs/`: Generated API docs land in `docs/api/` (produced by `make docs`).

## Architecture

### Evaluation Pipeline

A Datalog program flows through: base data is loaded into a `Relation` (`relation.zig`). Derived predicates use a `Variable` (`variable.zig`) driven
by an `Iteration` (`iteration.zig`) loop that calls `changed()` until a fixed point. Each iteration extends tuples via `join` (`join.zig`) or `extend`
(`extend.zig`), optionally using indexes (`index.zig`) or aggregates (`aggregate.zig`). Every primitive takes a `std.mem.Allocator` directly; there is
no wrapper context type.

### Relations and Variables Split

- `relation.zig` is the immutable, sorted, deduplicated tuple container used for base facts and finalized results.
- `variable.zig` is the mutable counterpart used inside fixed-point loops; it tracks stable, recent, and to-add tuple sets for semi-naive evaluation.
- New join shapes go in `join.zig`. New leaper-style extensions go in `extend.zig`.

### Indexing and Aggregation

`index.zig` provides keyed lookups used by the extend primitives. `aggregate.zig` provides group-by reductions.
When adding a new join or extension shape, consider whether it needs an index variant and add it alongside the existing ones.

### Public API Surface

Everything re-exported from `src/lib.zig` is part of the public API.
Changes to names or signatures there are breaking.
The rest of `src/zodd/` is internal and may be refactored freely as long as the public surface and its behavior are preserved.

### Dependencies

Zodd depends on two sibling Zig packages declared in `build.zig.zon`:

- `ordered`: sorted container primitives, linked into the `zodd` module for all builds.
- `minish`: property-testing framework, used only by `tests/property_tests.zig` and lazy-loaded in `build.zig`.

Please do not add further dependencies without prior discussion.

## Zig Conventions

- Zig version: 0.16.0 (as declared in `build.zig.zon` and the Makefile's `ZIG_LOCAL` path). CI pins the version declared in `build.zig.zon`.
- Formatting is enforced by `zig fmt`. Run `make format` before committing.
- Naming follows Zig standard-library conventions: `camelCase` for functions (e.g. `joinInto`, `extendInto`, `fromSlice`), `snake_case` for local
variables and struct fields, `PascalCase` for types and structs (e.g. `Relation`, `Variable`, `ExecutionContext`), and `SCREAMING_SNAKE_CASE` for
top-level compile-time constants.

## Required Validation

Run the relevant targets for any change:

| Target | Command | What It Runs |
|----------------|------------------------------------------------|-----------------------------------------------------------------------|
| Unit tests | `make test` | Inline `test` blocks in `src/` plus every file under `tests/` |
| Lint | `make lint` | Checks Zig formatting with `zig fmt --check` over `src/` and `tests/` |
| Examples | `make example` | Builds and runs every example under `examples/` |
| Single example | `make example EXAMPLE=e1_network_reachability` | Runs one example program |
| Docs | `make docs` | Generates API docs into `docs/api` |
| Everything | `make all` | Runs `build`, `test`, `lint`, and `docs` |

## First Contribution Flow

1. Read the relevant module under `src/zodd/` (often `relation.zig`, `variable.zig`, `join.zig`, or `extend.zig`).
2. Implement the smallest change that covers the requirement.
3. Add or update inline `test` blocks in the changed Zig module, or extend a test file under `tests/`, to cover the new behavior.
4. Run `make test` and `make lint`.
5. If public behavior changed, also run `make example` to ensure no example regresses.

Good first tasks:

- Add a new join or extension shape in `src/zodd/join.zig` or `src/zodd/extend.zig` (with an inline `test` block and, if appropriate, an integration
test under `tests/`).
- Improve an existing index strategy in `src/zodd/index.zig`.
- Add a new aggregate operation in `src/zodd/aggregate.zig`.
- Add a new example under `examples/` demonstrating a Datalog pattern, and list it in `examples/README.md`.

## Testing Expectations

- Unit tests live as inline `test` blocks in the module they cover (`src/lib.zig` and `src/zodd/*.zig`). They are discovered automatically via
`std.testing.refAllDecls(@This())` in `src/lib.zig`.
- Non-unit tests live under `tests/` (`integration_tests.zig`, `regression_tests.zig`, `property_tests.zig`, `incremental_tests.zig`) and are
auto-discovered by `build.zig`.
- Property tests use the `minish` dependency and should use fixed seeds so failures are reproducible in CI.
- Every new relation, variable operation, join, extension, index, or aggregate must ship with at least one `test` block that exercises it.
- No public API change is complete without a test covering the new or changed behavior.

## Change Design Checklist

Before coding:

1. Identify which module(s) the change touches (`relation`, `variable`, `iteration`, `join`, `extend`, `index`, `aggregate`, or `context`).
2. Consider whether a new join or extension needs a matching index or anti-variant.
3. Check whether the change is public-API-visible (i.e. re-exported from `src/lib.zig`); if so, treat it as a breaking or additive API change
deliberately.
4. Check cross-platform implications, especially for anything that touches the filesystem, timing, or OS-specific types.

Before submitting:

1. `make test` passes.
2. `make lint` passes.
3. `make example` still succeeds when touching relations, variables, joins, extensions, or iteration.
4. Docs updated (`make docs`) if the public API surface changed, and `ROADMAP.md` ticked/updated if a listed item was implemented.

## Commit and PR Hygiene

- Keep commits scoped to one logical change.
- PR descriptions should include:
1. Behavioral change summary.
2. Tests added or updated.
3. Whether examples were run locally (yes/no), and on which OS.
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ would like to work on or if it has already been resolved.

### Development Workflow

> [!IMPORTANT]
> If you're using an AI-assisted coding tool like Claude Code or Codex, make sure the AI follows the instructions in the [AGENTS.md](AGENTS.md) file.

#### Prerequisites

Install GNU Make on your system if it's not already installed.
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
################################################################################
# Configuration and Variables
################################################################################
ZIG ?= $(shell which zig || echo ~/.local/share/zig/0.15.2/zig)
ZIG_LOCAL := $(HOME)/.local/share/zig/0.16.0/zig
ZIG ?= $(shell test -x $(ZIG_LOCAL) && echo $(ZIG_LOCAL) || which zig)
ZIG_VERSION := $(shell $(ZIG) version)
BUILD_TYPE ?= Debug
BUILD_OPTS = -Doptimize=$(BUILD_TYPE)
Expand Down
36 changes: 14 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
[![License](https://img.shields.io/badge/license-MIT-007ec6?label=license&style=flat&labelColor=282c34&logo=open-source-initiative)](https://github.com/CogitatorTech/zodd/blob/main/LICENSE)
[![Examples](https://img.shields.io/badge/examples-view-green?style=flat&labelColor=282c34&logo=zig)](https://github.com/CogitatorTech/zodd/tree/main/examples)
[![Docs](https://img.shields.io/badge/docs-read-blue?style=flat&labelColor=282c34&logo=read-the-docs)](https://CogitatorTech.github.io/zodd/)
[![Zig Version](https://img.shields.io/badge/Zig-0.15.2-orange?logo=zig&labelColor=282c34)](https://ziglang.org/download/)
[![Zig](https://img.shields.io/badge/zig-0.16.0-F7A41D?style=flat&labelColor=282c34&logo=zig)](https://ziglang.org/download/)
[![Release](https://img.shields.io/github/release/CogitatorTech/zodd.svg?label=release&style=flat&labelColor=282c34&logo=github)](https://github.com/CogitatorTech/zodd/releases/latest)

A small embeddable Datalog engine in Zig
Expand Down Expand Up @@ -78,7 +78,6 @@ For example:
- Written in pure Zig with a simple API
- Implements semi-naive evaluation for efficient recursive query processing
- Uses immutable, sorted, and deduplicated relations as core data structures
- Supports parallel execution for joins and variable updates
- Provides primitives for multi-way joins, anti-joins, secondary indexes, and aggregation

See [ROADMAP.md](ROADMAP.md) for the list of implemented and planned features.
Expand All @@ -105,7 +104,7 @@ Replace `<branch_or_tag>` with the desired branch or release tag, like `main` (f
This command will download Zodd and add it to Zig's global cache and update your project's `build.zig.zon` file.

> [!NOTE]
> Zodd is developed and tested with Zig version 0.15.2.
> Zodd is developed and tested with Zig version 0.16.0.

#### Adding to Build Script

Expand All @@ -130,49 +129,42 @@ const std = @import("std");
const zodd = @import("zodd");

pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
var gpa = std.heap.DebugAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
var ctx = try zodd.ExecutionContext.initWithThreads(allocator, 4);
defer ctx.deinit();

const Edge = struct { u32, u32 };

// Create base relation: edges in a graph
var edges = try zodd.Relation(Edge).fromSlice(&ctx, &[_]Edge{
// Base relation: edges in a graph
var edges = try zodd.Relation(Edge).fromSlice(allocator, &[_]Edge{
.{ 1, 2 },
.{ 2, 3 },
.{ 3, 4 },
});
defer edges.deinit();

// Create variable for reachability (transitive closure)
var reachable = zodd.Variable(Edge).init(&ctx);
// Variable holding the reachability closure
var reachable = zodd.Variable(Edge).init(allocator);
defer reachable.deinit();
try reachable.insertSlice(edges.elements);

// Initialize with base edges
try reachable.insertSlice(&ctx, edges.elements);

// Fixed-point iteration: reachable(X,Z) :- reachable(X,Y), edge(Y,Z)
// Fixed-point iteration: reachable(X, Z) :- reachable(X, Y), edge(Y, Z)
while (try reachable.changed()) {
var new_tuples = std.ArrayList(Edge).init(allocator);
defer new_tuples.deinit();
var batch: std.ArrayList(Edge) = .empty;
defer batch.deinit(allocator);

for (reachable.recent.elements) |r| {
for (edges.elements) |e| {
if (e[0] == r[1]) {
try new_tuples.append(.{ r[0], e[1] });
}
if (e[0] == r[1]) try batch.append(allocator, .{ r[0], e[1] });
}
}

if (new_tuples.items.len > 0) {
const rel = try zodd.Relation(Edge).fromSlice(&ctx, new_tuples.items);
if (batch.items.len > 0) {
const rel = try zodd.Relation(Edge).fromSlice(allocator, batch.items);
try reachable.insert(rel);
}
}

// Get final result
var result = try reachable.complete();
defer result.deinit();

Expand Down
4 changes: 2 additions & 2 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ This document outlines the features implemented in Zodd and the future goals for
- [x] Persistence
- [x] Secondary indices
- [x] Incremental maintenance
- [x] Parallel execution
- [ ] CLI interface
- [ ] Parallel execution
- [ ] CLI
- [ ] Streaming input
- [ ] Rule DSL
- [ ] Query planner
Expand Down
Loading
Loading