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
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@

version: 2
updates:
- package-ecosystem: "cargo"
directory: "/rust"
cooldown:
default-days: 7
groups:
rust-dependencies:
update-types:
- "minor"
- "patch"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "gomod"
directories:
- "**/*"
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v6

- uses: actions/setup-go@v6
with:
go-version: "1.27"

- name: Setup sqlc
uses: sqlc-dev/setup-sqlc@v5
with:
Expand All @@ -428,6 +432,9 @@ jobs:
echo "Make sure migration directories are the same"
make verify/migrations

- name: Verify Rust migrations match
run: make verify/rust-migrations

- name: Verify sqlc generated code
run: |
echo "Make sure that all sqlc changes are checked in"
Expand Down
156 changes: 156 additions & 0 deletions .github/workflows/rust.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
name: Rust

on:
# Filter the whole workflow so unrelated changes don't create skipped
# matrix jobs. Keep both events' paths in sync.
push:
branches:
- master
paths:
- ".github/workflows/rust.yaml"
- "Makefile"
- "internal/cmd/syncrustmigrations/**"
- "riverdriver/riverpgxv5/migration/**"
- "riverdriver/riversqlite/migration/**"
- "rust/**"
pull_request:
paths:
- ".github/workflows/rust.yaml"
- "Makefile"
- "internal/cmd/syncrustmigrations/**"
- "riverdriver/riverpgxv5/migration/**"
- "riverdriver/riversqlite/migration/**"
- "rust/**"

permissions:
contents: read

env:
# Keep the cross-run cache small; Cargo still reuses compiled dependencies.
CARGO_INCREMENTAL: "0"

jobs:
quality:
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
id: rust
with:
components: clippy,rustfmt
- name: Cache Rust dependencies and build artifacts
uses: actions/cache@v5
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
rust/target
key: rust-v1-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ steps.rust.outputs.cachekey }}-${{ hashFiles('rust/**/Cargo.toml', 'rust/Cargo.lock') }}
restore-keys: rust-v1-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ steps.rust.outputs.cachekey }}-
- uses: taiki-e/install-action@v2
with:
tool: cargo-deny@0.20.2
- uses: taiki-e/install-action@v2
with:
tool: cargo-semver-checks@0.49.0

- name: Lint, including PostgreSQL-only and SQLite-only builds
run: make lint/rust

- name: Documentation and examples
run: make doc/rust

- name: Dependency and license policy
run: make check/rust/dependencies

- name: Package archives
run: make check/rust/package

# Compare with the latest published Rust release tag, which the full
# checkout above includes. Before the first release there is no
# baseline and the step reports that instead of failing.
- name: Public API compatibility
run: make check/rust/semver

# docs.rs builds with a nightly toolchain and `--cfg docsrs`, which
# enables the crates' `doc_cfg` feature badges. Last, since installing
# nightly makes it the default toolchain for later steps.
- uses: dtolnay/rust-toolchain@nightly
- name: Documentation as docs.rs builds it
run: make doc/rust/docsrs

rust_versions:
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
rust-version: ["1.95", "1.96", "1.97"]

steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@master
id: rust
with:
toolchain: ${{ matrix.rust-version }}
- name: Cache Rust dependencies and build artifacts
uses: actions/cache@v5
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
rust/target
key: rust-v1-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ steps.rust.outputs.cachekey }}-${{ hashFiles('rust/**/Cargo.toml', 'rust/Cargo.lock') }}
restore-keys: rust-v1-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ steps.rust.outputs.cachekey }}-
- name: Check every target and feature
run: cargo check --manifest-path rust/Cargo.toml --workspace --all-targets --all-features --locked
- name: Unit, doc, and SQLite tests
run: make test/rust/sqlite

postgres:
runs-on: ubuntu-latest
timeout-minutes: 20
strategy:
fail-fast: false
matrix:
postgres-version: [14, 15, 16, 17, 18]
env:
RIVER_RUST_DATABASE_URL: postgres://postgres:postgres@localhost:5432/river_rust_test?sslmode=disable

services:
postgres:
image: postgres:${{ matrix.postgres-version }}
env:
POSTGRES_PASSWORD: postgres
options: >-
--health-cmd pg_isready
--health-interval 2s
--health-timeout 5s
--health-retries 5
ports:
- 5432:5432

steps:
- uses: actions/checkout@v6
- uses: dtolnay/rust-toolchain@stable
id: rust
- name: Cache Rust dependencies and build artifacts
uses: actions/cache@v5
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
rust/target
key: rust-v1-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ steps.rust.outputs.cachekey }}-${{ hashFiles('rust/**/Cargo.toml', 'rust/Cargo.lock') }}
restore-keys: rust-v1-${{ runner.os }}-${{ runner.arch }}-${{ github.job }}-${{ steps.rust.outputs.cachekey }}-
- name: Create test database
run: PGPASSWORD=postgres createdb -h localhost -U postgres river_rust_test
- name: Rust unit, SQLite, and PostgreSQL tests
run: make test/rust
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
/river
/riverdriver/riverdrivertest/example_libsql_test.libsql
/sqlite/
/rust/**/target/
108 changes: 102 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
.DEFAULT_GOAL := help

SQLC ?= sqlc

.PHONY: db/reset
db/reset: ## Drop, create, and migrate dev and test databases
db/reset: db/reset/dev
Expand All @@ -18,17 +20,22 @@ db/reset/test: ## Drop, create, and migrate test databases
.PHONY: generate
generate: ## Generate generated artifacts
generate: generate/migrations
generate: generate/rust-migrations
generate: generate/sqlc

.PHONY: generate/migrations
generate/migrations: ## Sync changes of pgxv5 migrations to database/sql
rsync -au --delete "riverdriver/riverpgxv5/migration/" "riverdriver/riverdatabasesql/migration/"

.PHONY: generate/rust-migrations
generate/rust-migrations: ## Sync database migrations to Rust
go run ./internal/cmd/syncrustmigrations

.PHONY: generate/sqlc
generate/sqlc: ## Generate sqlc
cd riverdriver/riverdatabasesql/internal/dbsqlc && sqlc generate
cd riverdriver/riverpgxv5/internal/dbsqlc && sqlc generate
cd riverdriver/riversqlite/internal/dbsqlc && sqlc generate
cd riverdriver/riverdatabasesql/internal/dbsqlc && $(SQLC) generate
cd riverdriver/riverpgxv5/internal/dbsqlc && $(SQLC) generate
cd riverdriver/riversqlite/internal/dbsqlc && $(SQLC) generate

# Looks at comments using ## on targets and uses them to produce a help output.
.PHONY: help
Expand All @@ -42,6 +49,8 @@ help: ## Print this message
submodules := $(shell go list -f '{{.Dir}}' -m)

ITERATIONS ?= 100
RUST_BENCH_ARGS ?=
RUST_SEMVER_BASELINE_REV ?= $(shell git tag --list 'riverqueue-v*' --sort=-v:refname | head -n 1)

TEST_DATABASE ?= all

Expand Down Expand Up @@ -72,6 +81,16 @@ define lint-target
endef
$(foreach mod,$(submodules),$(eval $(call lint-target,$(mod))))

# Rust targets are separate from `lint` and `test` so Go-only contributors and
# the Go CI jobs do not need a Rust toolchain; the Rust workflow runs them.
.PHONY: lint/rust
lint/rust: ## Run Rust formatting and clippy checks, including single-backend builds
cd rust && cargo fmt --all -- --check
cd rust && cargo clippy --workspace --all-targets --all-features --locked -- -D warnings
cd rust && cargo clippy -p riverqueue -p riverqueue-migrate -p riverqueue-cli -p riverqueue-test --no-default-features --features postgres --all-targets --locked -- -D warnings
cd rust && cargo clippy -p riverqueue -p riverqueue-migrate -p riverqueue-cli -p riverqueue-test --no-default-features --features sqlite --all-targets --locked -- -D warnings
cd rust && $(RUST_POSTGRES_TESTS_ENV) cargo clippy -p riverqueue -p riverqueue-migrate --all-targets --all-features --locked -- -D warnings

.PHONY: test
test:: ## Run tests (TEST_DATABASE=all, postgres, or sqlite)
define test-target
Expand All @@ -85,6 +104,74 @@ ifneq ($(TEST_DATABASE),sqlite)
test:: ; cd ./riverdriver/riverdrivertest && RIVER_USE_LEGACY_SUBTRANSACTIONS=1 go test . -run '^TestDriverRiverPgxV5$$/.*/WithTx$$' -timeout 2m
endif

# `--cfg river_postgres_tests` builds the Rust PostgreSQL integration tests.
# It goes to both rustc and rustdoc so any doctest gated on it runs too, and
# into its own target directory so switching it on and off doesn't rebuild
# the ordinary build's artifacts. The default is absolute: trybuild resolves a
# relative target directory from the macros crate's directory.
RUST_POSTGRES_TESTS_ENV = RUSTFLAGS="$$RUSTFLAGS --cfg river_postgres_tests" \
RUSTDOCFLAGS="$$RUSTDOCFLAGS --cfg river_postgres_tests" \
CARGO_TARGET_DIR="$${CARGO_TARGET_DIR:-$(CURDIR)/rust/target}/postgres-tests"

# PostgreSQL integration tests need RIVER_RUST_DATABASE_URL. Without it
# test/rust still runs unit, doc, and SQLite integration tests, and fails in CI
# so a missing URL cannot turn the PostgreSQL suite into a silent pass.
.PHONY: test/rust
test/rust: ## Run Rust unit and SQLite tests, plus PostgreSQL tests when RIVER_RUST_DATABASE_URL is set
@if [ -n "$$RIVER_RUST_DATABASE_URL" ]; then \
cd rust && $(RUST_POSTGRES_TESTS_ENV) cargo test --workspace --all-features --locked; \
elif [ -n "$$CI" ]; then \
echo "RIVER_RUST_DATABASE_URL is required in CI to run the Rust PostgreSQL tests" >&2; exit 1; \
else \
echo "RIVER_RUST_DATABASE_URL is unset; skipping Rust PostgreSQL integration tests"; \
cd rust && cargo test --workspace --features riverqueue/sqlite,riverqueue-migrate/sqlite --locked; \
fi

.PHONY: test/rust/postgres
test/rust/postgres: ## Run all Rust tests, including PostgreSQL integration tests (requires RIVER_RUST_DATABASE_URL)
@test -n "$$RIVER_RUST_DATABASE_URL" || { echo "RIVER_RUST_DATABASE_URL is required" >&2; exit 1; }
cd rust && $(RUST_POSTGRES_TESTS_ENV) cargo test --workspace --all-features --locked

.PHONY: test/rust/sqlite
test/rust/sqlite: ## Run Rust unit, doc, and SQLite integration tests without a PostgreSQL database
cd rust && cargo test --workspace --features riverqueue/sqlite,riverqueue-migrate/sqlite --locked

.PHONY: doc/rust
doc/rust: ## Build Rust API documentation, compiled examples, and doctests for each backend feature set
cd rust && RUSTDOCFLAGS="-D warnings" cargo doc --workspace --all-features --no-deps --locked
cd rust && RUSTDOCFLAGS="-D warnings" cargo test --workspace --all-features --doc --locked
cd rust && RUSTDOCFLAGS="-D warnings" cargo test -p riverqueue -p riverqueue-migrate -p riverqueue-cli -p riverqueue-test --no-default-features --features postgres --doc --locked
cd rust && RUSTDOCFLAGS="-D warnings" cargo test -p riverqueue -p riverqueue-migrate -p riverqueue-cli -p riverqueue-test --no-default-features --features sqlite --doc --locked
cd rust && cargo check --workspace --examples --all-features --locked

.PHONY: doc/rust/docsrs
doc/rust/docsrs: ## Build Rust API documentation as docs.rs does (nightly toolchain, `--cfg docsrs`)
cd rust && RUSTDOCFLAGS="--cfg docsrs -D warnings" CARGO_TARGET_DIR="$${CARGO_TARGET_DIR:-target}/docsrs" cargo +nightly doc -p riverqueue -p riverqueue-migrate -p riverqueue-test --all-features --no-deps --locked

.PHONY: check/rust/dependencies
check/rust/dependencies: ## Audit Rust advisories, licenses, bans, and sources
cd rust && cargo deny check

.PHONY: check/rust/package
check/rust/package: ## Build and verify publishable crate archives without publishing
cd rust && cargo package --workspace --allow-dirty --locked

# The baseline is the latest published riverqueue-v* tag, and
# cargo-semver-checks infers the allowed change from the version bump. It
# skips every lint while the workspace version is a pre-release, so
# comparing unreleased revisions with each other checks nothing. Until a
# Rust release is tagged the check reports that there is no baseline. Set
# RUST_SEMVER_BASELINE_REV to compare with another revision.
.PHONY: check/rust/semver
check/rust/semver: ## Check Rust APIs against RUST_SEMVER_BASELINE_REV (default: latest Rust tag)
@if test -z "$(RUST_SEMVER_BASELINE_REV)"; then \
echo "No published Rust release tag (riverqueue-v*); no public API baseline to compare"; \
elif ! git cat-file -e "$(RUST_SEMVER_BASELINE_REV):rust/Cargo.toml" 2>/dev/null; then \
echo "Baseline $(RUST_SEMVER_BASELINE_REV) predates the Rust crates; no public API to compare"; \
else \
cd rust && cargo semver-checks --workspace --baseline-rev "$(RUST_SEMVER_BASELINE_REV)"; \
fi

.PHONY: test/race
test/race:: ## Run tests with race detector (TEST_DATABASE=all, postgres, or sqlite)
define test-race-target
Expand All @@ -104,6 +191,10 @@ define bench-target
endef
$(foreach mod,$(submodules),$(eval $(call bench-target,$(mod))))

.PHONY: bench/rust
bench/rust: ## Run the destructive Rust PostgreSQL throughput benchmark
cd rust && cargo run --release --locked -p riverqueue-cli --bin riverqueue -- bench $(if $(DATABASE_URL),--database-url "$(DATABASE_URL)") $(RUST_BENCH_ARGS)

.PHONY: tidy
tidy:: ## Run `go mod tidy` for all submodules
define tidy-target
Expand All @@ -122,14 +213,19 @@ update-mod-version: ## Update River packages in all submodules to $VERSION
.PHONY: verify
verify: ## Verify generated artifacts
verify: verify/migrations
verify: verify/rust-migrations
verify: verify/sqlc

.PHONY: verify/migrations
verify/migrations: ## Verify synced migrations
diff -qr riverdriver/riverpgxv5/migration riverdriver/riverdatabasesql/migration

.PHONY: verify/rust-migrations
verify/rust-migrations: ## Verify Rust migrations match the canonical migrations
go run ./internal/cmd/syncrustmigrations -check

.PHONY: verify/sqlc
verify/sqlc: ## Verify generated sqlc
cd riverdriver/riverdatabasesql/internal/dbsqlc && sqlc diff
cd riverdriver/riverpgxv5/internal/dbsqlc && sqlc diff
cd riverdriver/riversqlite/internal/dbsqlc && sqlc diff
cd riverdriver/riverdatabasesql/internal/dbsqlc && $(SQLC) diff
cd riverdriver/riverpgxv5/internal/dbsqlc && $(SQLC) diff
cd riverdriver/riversqlite/internal/dbsqlc && $(SQLC) diff
Loading
Loading