solidb 2.0.2

A lightweight, high-performance structured database server written in Rust.
Documentation
[workspace]
members = ["clients/rust-client", "sdbql-core", "benchmarks"]
exclude = ["fuzz"]

[package]
name = "solidb"
version = "2.0.2"
edition = "2021"
# Floor imposed by the dependency tree (rust-rocksdb 0.46 declares 1.89.0).
# Enforced by the `msrv` CI job, which builds with exactly this toolchain.
rust-version = "1.89"
default-run = "solidb"
description = "A lightweight, high-performance structured database server written in Rust."
license-file = "LICENCE.md"
repository = "https://github.com/solisoft/solidb"
keywords = ["database", "nosql", "json", "rest-api", "rust"]
categories = ["database", "web-programming::http-server"]
include = ["src/**/*", "README.md", "LICENCE.md", "Cargo.toml", "docs/**/*"]

[dependencies]
# Async runtime
tokio = { version = "1.35", features = ["full"] }

# HTTP server
axum = { version = "0.8.7", features = ["multipart", "ws", "macros"] }
# Drive HTTP connections manually (instead of `axum::serve`) so we can set an
# HTTP/1 header-read timeout — see the multiplexed HTTP server in main.rs.
hyper-util = { version = "0.1", features = ["server-auto", "server-graceful", "service", "tokio", "http1", "http2"] }
tower = { version = "0.5.2", features = ["util"] }
tower-http = { version = "0.6.8", features = ["trace", "cors", "compression-gzip", "compression-zstd", "set-header"] }
async-stream = "0.3"
async-trait = "0.1"

# WebSocket Client (for global changefeed aggregation)

tokio-tungstenite = { version = "0.28", features = ["rustls-tls-native-roots"] }
url = "2.4"

# TLS termination for the API listener (--tls-cert/--tls-key). rustls keeps
# the build free of OpenSSL, same rationale as the outbound clients. PEM
# parsing uses rustls-pki-types' PemObject API — the `rustls-pemfile` crate
# is unmaintained (RUSTSEC-2025-0134) and fails cargo-deny.
tokio-rustls = { version = "0.26", default-features = false, features = ["ring", "tls12"] }

# JSON serialization
serde = { version = "1.0", features = ["derive"] }
serde_urlencoded = "0.7"
serde_json = "1.0"
# Hasher for SDBQL row contexts (per-row map, SipHash showed up at ~5%).
foldhash = "0.2"
serde_bytes = "0.11"
bincode = "1.3"
lz4_flex = "0.12"
base64 = "0.22"
base32 = "0.5"
jsonschema = "0.45"

# Error handling
anyhow = "1.0"
thiserror = "2.0"

# Logging
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter"] }

# Observability - OpenTelemetry trace context propagation
opentelemetry = { version = "0.17", features = ["trace"] }
tracing-opentelemetry = "0.17"

# CLI
clap = { version = "4.4", features = ["derive", "env"] }
dotenvy = "0.15"

# Utilities
uuid = { version = "1.6", features = ["v4", "v7", "serde"] }
hostname = "0.4"
chrono = { version = "0.4", features = ["serde"] }
chrono-tz = "0.10"
uuid7 = "1.0"
ulid = "1.1"
nanoid = "0.4"
regex = "1.10"
seahash = "4.1"  # Uniform hash distribution for sharding

# Storage
rust-rocksdb = "0.46"

# Authentication
argon2 = "0.5"
# Keep the aws_lc_rs backend: the `rust_crypto` alternative pulls in the `rsa` crate, which
# commit 7320477 deliberately removed (RUSTSEC-2023-0071, no fixed version). See the
# windows-gated aws-lc-rs entry below for how this stays buildable on windows-msvc.
jsonwebtoken = { version = "10.3", default-features = false, features = ["aws_lc_rs", "use_pem"] }
once_cell = "1.19"
rand_core = "0.6"
rand = "0.8"
hex = "0.4"
sha2 = "0.10"
hmac = "0.12"
x25519-dalek = { version = "2.0", features = ["static_secrets"] }
md5 = "0.8"
subtle = "2.6"

# Temp directories (needed by benchmark)
tempfile = "3.10"

# HTTP client (for benchmarks)
# rustls rather than the default native-tls: it keeps the build free of OpenSSL, which
# otherwise needs Perl + NASM on Windows. default-features = false drops http2 and charset
# along with native-tls, so both are re-added explicitly.
reqwest = { version = "0.13", default-features = false, features = [
    "json",
    "blocking",
    "multipart",
    "rustls",
    "http2",
    "charset",
    "system-proxy",
] }

# FUSE filesystem (requires macFUSE on macOS)
fuser = { version = "0.16", optional = true }

# Daemon mode (Unix only)
# daemonize crate removed - using custom implementation in src/daemon.rs
# daemonize = "0.5"
libc = "0.2"

# Parallel processing (for concurrent benchmarks)
rayon = "1.8"
csv = "1.4.0"

# CLI UX
indicatif = "0.18"
colored = "3.0"
rustyline = "17"

# System info for stats
sysinfo = "0.38"

# Prometheus metrics
prometheus = "0.14"
futures = { version = "0.3.31", features = ["std"] }

# Embedded Lua scripting
mlua = { version = "0.11.5", features = ["lua54", "vendored", "async", "serialize", "send"] }
rmp-serde = "1.3.1"

# Enhanced validation and sanitization
sanitizer = "1.0"
slug = "0.1"
# UNACCENT: ASCII folding of Latin letters (already pulled in by slug)
deunicode = "1.6"

# Image processing
image = { version = "0.25", default-features = false, features = ["jpeg", "png", "webp", "gif"] }

# Cookie handling
cookie = "0.18"
time = "0.3.47"

# Caching
lru = "0.18"
fastbloom = { version = "0.17.0", features = ["serde"] }
cuckoofilter = { version = "0.5", features = ["serde_support"] }
parking_lot = "0.12"
dashmap = "6"

# Self-update
flate2 = "1"           # Gzip decompression
tar = "0.4"            # Tar archive extraction

# CLI Script Development
notify = "8"           # File system watcher
toml = "1.0"           # Config file parsing
walkdir = "2"          # Directory traversal
similar = "2"          # Diff generation
rpassword = "7"        # Secure password input

# TUI
ratatui = "0.29"       # TUI framework
crossterm = "0.29"     # Cross-platform terminal backend
tui-textarea = { version = "0.7", features = ["search"] }   # Multi-line text editor widget

# Default binary
[[bin]]
name = "solidb"
path = "src/main.rs"

[[bin]]
name = "solidb-dump"
path = "src/bin/solidb-dump.rs"

[[bin]]
name = "solidb-restore"
path = "src/bin/solidb-restore.rs"

[profile.release]
lto = "thin"
codegen-units = 16
opt-level = 3

[profile.bench]
inherits = "release"

# Profile for `cargo test`, in CI and for targeted local runs.
#
# Each of the ~96 files in tests/ becomes its own binary statically linking all
# of src/ (4.4 MB across 303 files) plus RocksDB. Under [profile.release] that
# is ~96 thin-LTO link steps to run 18 seconds of tests: CI measured 95m 07s of
# compilation against 18.4s of execution.
#
# `[profile.release]` cannot simply be relaxed — the `build-binaries` and
# `docker` CI jobs ship what it produces — hence a separate profile. Note this
# writes to target/ci/ *in addition to* target/release/, so a box that builds
# both keeps two trees.
[profile.ci]
inherits = "release"
# The whole game. Under release's `lto = "thin"` the dependency rlibs carry
# bitcode and codegen is deferred to the final artifact, so ThinLTO re-runs
# across the ~400-crate graph once per test binary — ~96 times. With LTO off,
# dependencies codegen once to object code and each test binary is a plain link.
#
# This is also why switching profiles forces one cold rebuild of every
# dependency: bitcode rlibs and object rlibs have different fingerprints.
#
# `lto` may only be set at profile top level; cargo rejects it (like `panic`
# and `rpath`) inside a `[profile.*.package.*]` override.
lto = false
# Applies to the workspace crates and the test harnesses; the overrides below
# keep dependencies at 3. Not 0 — O0 emits *more* code because it stops
# inlining wrappers, which makes all ~96 links slower and the suite 5-20x
# slower to run. O1 is the point where compile time drops but the tests still
# execute in seconds.
#
# Safe because no test that actually runs depends on optimisation: the only
# files under tests/ that touch `Instant::now()` / `.elapsed()` are the five
# benchmarks, and those are `#[ignore]`d behind the `bench-tests` feature.
opt-level = 1
# Deliberately NOT set here:
#   codegen-units — release's 16 is right. ubuntu-latest has 4 vCPUs and cargo
#     already has ~100 independent targets to fill them, so raising it buys no
#     parallelism and costs cross-CGU inlining, i.e. a bigger rlib to link 96x.
#   strip — `inherits = "release"` gives debug = 0, and cargo already passes
#     `-Cstrip=debuginfo` at that debug level. Setting it is a no-op. (What
#     would shrink target/ is `strip = "symbols"`, which is not worth losing
#     panic backtrace symbolisation in the CI logs you read on a failure.)
#   panic — cargo ignores it for test targets since libtest needs unwind, so it
#     would apply only to the bins, and a panic=abort bin cannot share
#     dependency artifacts with panic=unwind tests: it would double the
#     dependency build.

# Dependencies stay fully optimised. Those 18 seconds of tests are spent almost
# entirely inside RocksDB, ring and aws-lc-sys, and an unoptimised RocksDB would
# trade the compile time we just saved straight back for runtime. They are built
# once and cached, so this costs nothing per run.
[profile.ci.package."*"]
opt-level = 3

# Whether the `"*"` glob above also matches workspace members that are path
# dependencies is the murky corner of cargo's override rules, and the whole
# saving depends on the answer. Restate the three crates that actually
# recompile on every run so the intent holds either way.
[profile.ci.package.solidb]
opt-level = 1

[profile.ci.package.sdbql-core]
opt-level = 1

[profile.ci.package.solidb-client]
opt-level = 1

[dev-dependencies]
tokio-test = "0.4"
solidb-client = { path = "clients/rust-client" }

# Memory allocator: jemalloc fragments far less than glibc malloc and returns
# freed memory to the OS. With one column family per collection plus many
# Tokio/RocksDB threads, glibc's per-thread arenas pin RSS at the allocation
# high-water mark (multi-GB) long after the memory is freed; jemalloc keeps
# RSS tracking the actual working set. Not used under MSVC (Windows).
[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { version = "0.6", features = [
    # Route C and C++ `malloc` through jemalloc too, not just Rust's
    # GlobalAlloc. Without this, jemalloc links with its `_rjem_` prefix and
    # serves Rust only, so RocksDB's C++ allocations — block cache, table
    # readers, memtables, iterators, compaction buffers, i.e. the bulk of a
    # large instance's memory — still go to glibc, and the per-thread arena
    # growth this dependency exists to avoid happens anyway.
    #
    # Measured on the same 613-collection checkpoint, importing 400k documents
    # across 200 collections, prod profile, twice:
    #
    #                       glibc     jemalloc
    #   peak RSS (VmHWM)   1881 MB     1356 MB
    #   glibc arenas 64MB       20            2
    #   still held after    1726 MB (swapped out, never returned)
    #
    # Note the symbol-name coupling: this feature makes the tuning symbol in
    # main.rs plain `malloc_conf` instead of `_rjem_malloc_conf`. Change one
    # without the other and the tuning is silently lost.
    "unprefixed_malloc_on_supported_platforms",
] }
# Read back the allocator's effective options at startup. The tuning in
# `main.rs` is a link-time symbol: if the symbol name ever stops matching what
# jemalloc looks for, it silently does nothing. Logging the values it actually
# runs with turns that into something visible.
#
# The `stats` feature turns on jemalloc's own byte accounting, which /metrics
# exposes as solidb_jemalloc_*_bytes. It is what separates live data from
# fragmentation and from address space merely kept mapped — a process holding
# 21.7 GB resident was seen with 113 GB virtual, and without `stats.retained`
# there is no way to tell those apart. The flag propagates to
# tikv-jemalloc-sys, so the C library is built with its counters enabled;
# jemalloc documents that as low-overhead, but it is not free.
tikv-jemalloc-ctl = { version = "0.6", features = ["stats"] }

# aws-lc-sys (pulled in by jsonwebtoken and by reqwest's rustls provider) assembles its
# x86_64 asm with NASM on windows-msvc, which is not on the GitHub runner image. The
# prebuilt-nasm feature uses the object files shipped in the crate instead, leaving CMake +
# MSVC as the only build requirements — both preinstalled on windows-latest. Feature
# unification applies this to every aws-lc-rs in the graph.
[target.'cfg(windows)'.dependencies]
aws-lc-rs = { version = "1", features = ["prebuilt-nasm"] }

[features]
fuse = ["dep:fuser"]
# The five files in tests/ that are benchmarks rather than tests. Off by
# default, so `cargo test` does not build them — see the [[test]] blocks below.
bench-tests = []

# Benchmarks living in tests/. Every test in these five files is
# `#[test] #[ignore]`, so they were paying a full release link each and never
# running — five of the ~96 link steps for nothing. They are also the only
# files in tests/ that measure wall-clock time (`Instant::now()` / `elapsed()`),
# which is exactly why they must not be run as part of the suite.
#
# Declaring these explicitly does not disable autodiscovery of the other ~91
# files (`autotests` stays true), so nothing else needs listing here.
#
# Run them with:
#   cargo test --profile ci --features bench-tests -- --ignored
[[test]]
name = "benchmark_sort"
path = "tests/benchmark_sort.rs"
required-features = ["bench-tests"]

[[test]]
name = "benchmark_perf_fixes"
path = "tests/benchmark_perf_fixes.rs"
required-features = ["bench-tests"]

[[test]]
name = "sdbql_compare_bench"
path = "tests/sdbql_compare_bench.rs"
required-features = ["bench-tests"]

[[test]]
name = "sdbql_datetime_bench"
path = "tests/sdbql_datetime_bench.rs"
required-features = ["bench-tests"]

[[test]]
name = "sdbql_string_bench"
path = "tests/sdbql_string_bench.rs"
required-features = ["bench-tests"]

[[bin]]
name = "solidb-fuse"
path = "src/bin/solidb-fuse.rs"
required-features = ["fuse"]

[[bin]]
name = "solidb-repl"
path = "src/bin/solidb-repl.rs"

[workspace.metadata.release]
sign-commit = false
sign-tag = false
push-remote = "origin"
consolidate-commits = false
pre-release-commit-message = "chore: release v{{version}}"
tag-message = "{{tag_name}}"
tag-name = "v{{version}}"
pre-release-replacements = []
publish = false