{"id":50875423,"url":"https://github.com/notthatbreezy/babar","last_synced_at":"2026-06-15T09:30:50.292Z","repository":{"id":354941726,"uuid":"1221193794","full_name":"notthatbreezy/babar","owner":"notthatbreezy","description":"Rust library for type safe postgres interaction","archived":false,"fork":false,"pushed_at":"2026-05-01T02:53:57.000Z","size":11274,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-05-01T04:19:54.452Z","etag":null,"topics":["postgresql","rust","tokio"],"latest_commit_sha":null,"homepage":"https://babar.notthatbreezy.io","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/notthatbreezy.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-25T21:54:28.000Z","updated_at":"2026-05-01T02:54:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/notthatbreezy/babar","commit_stats":null,"previous_names":["notthatbreezy/babar"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/notthatbreezy/babar","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notthatbreezy%2Fbabar","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notthatbreezy%2Fbabar/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notthatbreezy%2Fbabar/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notthatbreezy%2Fbabar/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/notthatbreezy","download_url":"https://codeload.github.com/notthatbreezy/babar/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/notthatbreezy%2Fbabar/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34357281,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-15T02:00:07.085Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["postgresql","rust","tokio"],"created_at":"2026-06-15T09:30:49.321Z","updated_at":"2026-06-15T09:30:50.265Z","avatar_url":"https://github.com/notthatbreezy.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# babar\n\nTyped, async PostgreSQL driver for Tokio that speaks the PostgreSQL wire protocol directly.\n\n`babar` is explicit: queries and commands are typed values, schema-aware `query!` / `command!` are the default typed SQL path, `typed_query!` remains available as a compatibility alias during the unification transition, `sql!` is the lower-level fragment builder, `#[derive(Codec)]` infers common struct fields and lets `#[pg(codec = \"...\")]` override the outliers, and a background driver task owns the socket so public API calls stay cancellation-safe.\n\n## Highlights\n\n- direct wire-protocol implementation on Tokio — no `libpq`, no `tokio-postgres`\n- typed `Query`, `Command`, `PreparedQuery`, `PreparedCommand`, `Transaction`/`Savepoint`, and `Pool` APIs\n- typed binary `CopyIn\u003cT\u003e` for `COPY FROM STDIN` bulk ingest from `Vec\u003cT\u003e` / iterators\n- schema-aware typed SQL with `query!` / `command!`, authored schemas via `schema!`, and `typed_query!` as a compatibility alias during migration\n- rich errors with SQLSTATE fields plus SQL/caret rendering\n- OpenTelemetry-friendly `tracing` spans: `db.connect`, `db.prepare`, `db.execute`, `db.transaction`\n- TLS via `rustls` (default) or `native-tls`\n\n## Built-in codecs\n\nThese ship in the core crate with no extra Cargo feature flag:\n\n| Codec family | Included surface |\n| --- | --- |\n| integers | `int2`, `int4`, `int8` |\n| floating point | `float4`, `float8` |\n| booleans | `bool` |\n| text / strings | `text`, `varchar`, `bpchar` |\n| binary | `bytea` |\n| nullability | `nullable(codec)` |\n| composition | tuple codecs (arities 1-16) |\n\n## Optional codecs enabled via feature flags\n\n| Feature | Purpose | On by Default |\n| --- | --- | --- |\n| `uuid` | `uuid::Uuid` codecs | ❌ |\n| `time` | `time` date/time codecs | ❌ |\n| `chrono` | `chrono` date/time codecs | ❌ |\n| `json` | `json`, `jsonb`, typed JSON codecs | ❌ |\n| `numeric` | `rust_decimal::Decimal` codec | ❌ |\n| `net` | `inet` / `cidr` codecs | ❌ |\n| `interval` | PostgreSQL interval codec | ❌ |\n| `array` | binary array codec/combinators | ❌ |\n| `range` | binary range codec/combinators | ❌ |\n| `postgis` | PostGIS `geometry` / `geography` codecs for common 2D `geo-types` shapes | ❌ |\n| `pgvector` | `Vector` wrapper plus dynamic-`vector` codec | ❌ |\n| `text-search` | `TsVector` / `TsQuery` wrappers plus text-search codecs | ❌ |\n| `macaddr` | `macaddr` / `macaddr8` codecs with `MacAddr` / `MacAddr8` values | ❌ |\n| `bits` | `bit` / `varbit` codecs with explicit `BitString` length tracking | ❌ |\n| `hstore` | `hstore` codec backed by a stable `Hstore` map wrapper | ❌ |\n| `citext` | `citext` codec value mapped to Rust `String` | ❌ |\n| `multirange` | binary multirange codec/combinators layered on `Range` | ❌ |\n\nAdvanced codecs now mix fixed-OID families (`macaddr`, `bits`) with\nextension-resolved families (`hstore`, `citext`, `postgis`). The `postgis`\nfeature now ships binary PostGIS codecs on top of that dynamic type-resolution\npath: `geo-types` values stay primary, while babar's `Geometry\u003cT\u003e` /\n`Geography\u003cT\u003e` wrappers carry optional `Srid` metadata and keep the SQL type\ndistinction explicit. v1 deliberately supports common 2D shapes (`Point`,\n`LineString`, `Polygon`, `MultiPoint`, `MultiLineString`, `MultiPolygon`) and\ndoes not yet cover Z/M geometries, `GeometryCollection`, or PostgreSQL's\nbuilt-in geometric types. The `multirange` feature builds directly on the same\n`Range\u003cT\u003e` model used by the `range` family, adding a thin `Multirange\u003cT\u003e`\nwrapper rather than a separate shape.\n\nImportant caveats for the new families:\n\n- `postgis`, `pgvector`, `hstore`, and `citext` require the matching PostgreSQL\n  extension to be installed in the target database.\n- `pgvector` uses a dedicated `Vector` wrapper, requires at least one finite\n  `f32` dimension, and resolves the extension OID dynamically per session.\n- `text-search` intentionally keeps `TsVector` / `TsQuery` as canonical SQL text\n  wrappers in v0.1 rather than exposing a parsed Rust AST.\n- `range` / `multirange` currently support PostgreSQL's built-in scalar range\n  families with binary inner codecs (`int4`, `int8`, `numeric`, `date`,\n  `timestamp`, `timestamptz`); they are not a generic wrapper for arbitrary\n  extension types.\n\n## Quick start\n\n```rust,no_run\nuse babar::query::{Command, Query};\nuse babar::{Config, Session};\n\n#[derive(Debug, Clone, PartialEq, babar::Codec)]\nstruct DemoUser {\n    id: i32,\n    name: String,\n}\n\n#[derive(Debug, Clone, PartialEq, babar::Codec)]\nstruct MinUserId {\n    min_id: i32,\n}\n\nbabar::schema! {\n    mod app_schema {\n        table demo_users {\n            id: primary_key(int4),\n            name: text,\n        }\n    }\n}\n\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() -\u003e babar::Result\u003c()\u003e {\n    let cfg = Config::new(\"localhost\", 5432, \"postgres\", \"postgres\")\n        .password(\"secret\")\n        .application_name(\"babar-readme\");\n    let session = Session::connect(cfg).await?;\n\n    let create: Command\u003c()\u003e =\n        Command::raw(\"CREATE TEMP TABLE demo_users (id int4 PRIMARY KEY, name text NOT NULL)\");\n    session.execute(\u0026create, ()).await?;\n\n    let insert: Command\u003cDemoUser\u003e =\n        app_schema::command!(INSERT INTO demo_users (id, name) VALUES ($id, $name));\n    session\n        .execute(\n            \u0026insert,\n            DemoUser {\n                id: 1,\n                name: \"Ada\".to_string(),\n            },\n        )\n        .await?;\n\n    let select: Query\u003cMinUserId, DemoUser\u003e = app_schema::query!(\n        SELECT demo_users.id, demo_users.name\n        FROM demo_users\n        WHERE demo_users.id \u003e= $min_id\n        ORDER BY demo_users.id\n    );\n    let rows = session.query(\u0026select, MinUserId { min_id: 1 }).await?;\n    assert_eq!(\n        rows,\n        vec![DemoUser {\n            id: 1,\n            name: \"Ada\".to_string(),\n        }]\n    );\n\n    session.close().await?;\n    Ok(())\n}\n```\n\n## Development\n\nLocal commands that match every CI gate one-to-one. Run the **Pre-push checklist**\nbelow before `git push` to a PR branch — it covers everything CI runs and surfaces\nthe same failures.\n\n### Toolchain setup\n\n`babar`'s MSRV is in `Cargo.toml` under `rust-version`. CI tests both the MSRV\nfloor and current `stable`. To exercise both locally:\n\n```bash\n# Install rustup + the MSRV toolchain (one-time)\nMSRV=$(grep '^rust-version' Cargo.toml | cut -d'\"' -f2)\nrustup toolchain install \"$MSRV\" --profile minimal --component clippy,rustfmt\nrustup toolchain install stable --profile minimal --component clippy,rustfmt\n\n# Tools used by the hygiene job (one-time install; slow first build)\ncargo install --locked cargo-deny cargo-audit cargo-semver-checks cargo-msrv\ncargo install --locked mdbook\n```\n\n\u003e Running `cargo check` against your *current* toolchain does **not** catch\n\u003e `requires rustc X.Y` errors from transitive deps. Always run the MSRV toolchain\n\u003e for that gate (the pre-push checklist below does it for you).\n\n### Local Postgres for tests and tutorials\n\nMost chapters in [`docs/`](docs/) and the integration tests assume a local\nPostgres reachable on `localhost:5432`. Run one in the foreground with verbose\nquery logging so you can watch every statement land:\n\n```bash\ndocker run --rm -it \\\n  --name babar-pg \\\n  -p 5432:5432 \\\n  postgres:17 \\\n  -c log_statement=all \\\n  -c log_destination=stderr \\\n  -c log_min_duration_statement=0 \\\n  -c log_connections=on \\\n  -c log_disconnections=on\n```\n\nDefault credentials baked into the image: user `postgres`, password `postgres`,\ndb `postgres`. Connection string: `postgres://postgres:postgres@localhost:5432/postgres`.\nCtrl-C kills the container; `--rm` discards data — exactly what you want for\nlocal dev.\n\n### Pre-push checklist\n\nThis block reproduces every CI gate. Run it from the repo root before pushing\nto any branch with an open PR:\n\n```bash\nMSRV=$(grep '^rust-version' Cargo.toml | cut -d'\"' -f2)\n\n# 1. Format (CI: lint job)\ncargo fmt --check\n\n# 2. Clippy on stable AND MSRV with -D warnings (CI: lint job)\ncargo +stable clippy --all-targets --all-features -- -D warnings\ncargo +\"$MSRV\" clippy --all-targets --all-features -- -D warnings\n\n# 3. Rustdoc with -D warnings (CI: lint job)\nRUSTDOCFLAGS=\"-D warnings\" cargo doc --workspace --no-deps\n\n# 4. Tests on MSRV AND stable (CI: test matrix)\ncargo +\"$MSRV\" test --all-features\ncargo +stable test --all-features\n\n# 5. Hygiene (CI: hygiene job)\ncargo deny check\ncargo audit\ncargo msrv verify --manifest-path crates/core/Cargo.toml --all-features -- cargo check --all-features\ncargo msrv verify --manifest-path crates/macros/Cargo.toml -- cargo check\ncargo semver-checks --workspace --baseline-rev origin/main\ncargo publish --dry-run --allow-dirty -p babar-macros\n\n# 6. mdbook builds clean (CI: pages workflow)\nmdbook build\n```\n\nIf any step fails, fix it locally first — don't push and let CI catch it. The\nmatrix is intentionally redundant: `cargo +stable clippy` and `cargo +$MSRV\nclippy` can disagree (newer rustc adds new lints; older deps may lint\ndifferently). CI runs both, so you should too.\n\n### Faster iteration loops\n\nThe full checklist takes a few minutes from a cold cache. While iterating on a\nsingle change, `cargo check --all-features` and `cargo test -p \u003ccrate\u003e` are\nfine; just run the full block before push.\n\nFor doc-only changes, only steps 3 and 6 are required. For source-only changes\nthat don't touch `Cargo.toml` / `Cargo.lock`, you can skip step 5's\n`cargo audit` / `cargo deny` (they validate the dependency graph, which hasn't\nmoved).\n\n### Common failures\n\n- **`feature edition2024 is required`** — a transitive dep needs a newer rustc\n  than your MSRV floor. Either bump `rust-version` in `Cargo.toml` (and the CI\n  matrix in `.github/workflows/ci.yml`) or pin the offending crate via\n  `cargo update -p \u003cname\u003e --precise \u003colder-version\u003e`.\n- **`-D warnings` clippy failure that doesn't reproduce** — run with\n  `cargo +stable` *and* `cargo +$MSRV`. Newer rustc adds lints that older\n  toolchains don't know about.\n- **`cargo publish --dry-run` failure** — usually a missing `description`,\n  `license`, or `repository` field, or a path-only dependency on a workspace\n  crate without a corresponding `version =`. `babar-macros` can be verified\n  directly; `babar` itself must wait until `babar-macros` is visible in the\n  crates.io index.\n\n### Continuous integration\n\nCI is defined in [`.github/workflows/ci.yml`](.github/workflows/ci.yml) and\n[`.github/workflows/pages.yml`](.github/workflows/pages.yml). After pushing,\nread live status without leaving the terminal:\n\n```bash\ngh pr checks            # status of the PR linked to the current branch\ngh run watch            # follow the most recent run live\ngh run view --log-failed   # only the failed jobs' logs\n```\n\n## Tutorial\n\nFor a guided build from an empty directory, start with\n[`docs/tutorials/postgres-api-from-scratch.md`](docs/tutorials/postgres-api-from-scratch.md).\nIt is the long-form path for readers with basic Rust experience and little\nTokio background who want to build a small Postgres-backed API with Axum,\nbabar, and Dial9-backed observability. The README stays focused on reference\nmaterial; the tutorial owns the end-to-end walkthrough.\n\nThe same tutorial is published via GitHub Pages at\n[`https://babar.notthatbreezy.io`](https://babar.notthatbreezy.io).\n\n## Compile-time SQL verification\n\n`babar`'s primary typed SQL surface is now schema-aware `query!` /\n`command!`:\n\n```rust\nuse babar::query::{Command, Query};\n\n#[derive(Debug, Clone, PartialEq, babar::Codec)]\nstruct LookupArgs {\n    min_id: i32,\n}\n\n#[derive(Debug, Clone, PartialEq, babar::Codec)]\nstruct UserRow {\n    id: i32,\n    name: String,\n}\n\n#[derive(Debug, Clone, PartialEq, babar::Codec)]\nstruct NewUser {\n    id: i32,\n    name: String,\n    active: bool,\n}\n\nlet lookup: Query\u003cLookupArgs, UserRow\u003e = babar::query!(\n    schema = {\n        table public.users {\n            id: primary_key(int4),\n            name: text,\n            active: bool,\n        },\n    },\n    params = LookupArgs,\n    row = UserRow,\n    SELECT users.id, users.name\n    FROM users\n    WHERE users.id \u003e= $min_id AND users.active = true\n);\n\nlet insert: Command\u003cNewUser\u003e = babar::command!(\n    schema = {\n        table public.users {\n            id: primary_key(int4),\n            name: text,\n            active: bool,\n        },\n    },\n    params = _,\n    INSERT INTO users (id, name, active) VALUES ($id, $name, $active)\n);\n\nbabar::schema! {\n    mod app_schema {\n        table public.users {\n            id: primary_key(int4),\n            name: text,\n            active: bool,\n        },\n    }\n}\n\nlet lookup: Query\u003cLookupArgs, UserRow\u003e = app_schema::query!(\n    params = _,\n    row = _,\n    SELECT users.id, users.name\n    FROM users\n    WHERE users.id \u003e= $min_id AND users.active = true\n);\n```\n\n`query!` / `command!` now share the same schema-aware compiler:\n\n- use inline `schema = { ... }` blocks for one-off examples and tests\n- use `schema! { mod app_schema { ... } }` plus `app_schema::query!(...)` /\n  `app_schema::command!(...)` for reusable authored schemas\n- `typed_query!` remains available as a compatibility alias to the same\n  compiler during this transition, and schema modules also re-export\n  `typed_query!` / `typed_command!`\n- during macro expansion, babar first checks `BABAR_DATABASE_URL`, then\n  `DATABASE_URL`\n- today, live verification runs for schema-aware `SELECT` statements\n  (`query!`, `typed_query!`, and schema-scoped wrappers), checking authored\n  schema facts, parameters, and returned columns against a live PostgreSQL\n  server\n- non-`RETURNING` `command!` calls and explicit-`RETURNING` DML are not yet\n  probe-verified through that path\n- without config, the macros still compile and emit the same runtime `Query` /\n  `Command` values\n- v0.1 does not ship an offline cache, generated schema snapshot, file-based\n  schema input, or live schema introspection flow\n- unique table names stay available as `app_schema::users`; if two SQL schemas\n  share a table name, use namespaces like `app_schema::public::users`\n- authored fields stay type-first: `type_name`, `nullable(type_name)`,\n  `primary_key(type_name)`, and `pk(type_name)`\n- authored declarations accept `bool`, `bytea`, `varchar`, `text`, `int2`,\n  `int4`, `int8`, `float4`, `float8`, `uuid`, `date`, `time`, `timestamp`,\n  `timestamptz`, `json`, `jsonb`, and `numeric`\n- typed SQL currently lowers inferred parameters and projected columns across\n  that same family, including nullable variants; the matching babar feature\n  must still be enabled for families such as `uuid`, `time`, `json`, and\n  `numeric`\n- named placeholders reuse slots when repeated, and optional forms stay\n  explicit: `$value?` only for supported `WHERE` / `JOIN` comparisons or full\n  `LIMIT` / `OFFSET` expressions, `(...)?` only for a full parenthesized\n  `WHERE` / `JOIN` predicate or a single `ORDER BY` expression\n\nThe supported subset is intentionally small. v1 expects exactly one statement\nand keeps reads narrow: explicit projections, one `FROM` relation plus optional\njoins, optional `WHERE` / `ORDER BY` / `LIMIT` / `OFFSET`, and no `SELECT *`,\n`WITH` / CTEs, subqueries, `DISTINCT`, `GROUP BY` / `HAVING`, set operations,\nor multi-statement input. Writes are limited to `INSERT ... VALUES`,\n`UPDATE ... WHERE`, and `DELETE ... WHERE`, with explicit-column `RETURNING`\nlowering through the query-shaped row path. v1 does not cover\n`INSERT ... SELECT`, `ON CONFLICT`, `UPDATE ... FROM`, `DELETE ... USING`,\nwildcard `RETURNING *`, or `UPDATE` / `DELETE` without a `WHERE` predicate. It\nis not a general SQL rewrite engine, ORM, or codegen workflow.\n\nSchema-aware typed SQL can also pin or expose the struct contract at the macro\nsite:\n\n- `query!(schema = { ... }, params = LookupArgs, row = UserRow, SELECT ...)`\n- `command!(schema = { ... }, params = NewUser, INSERT ...)`\n- `params = _` / `row = _` when you want surrounding `Query\u003cA, B\u003e` /\n  `Command\u003cA\u003e` types to stay the source of inference\n- omit the selection entirely when the inferred tuple contract is the right fit\n\nExplicit `params = Type` / `row = Type` selections win over surrounding type\ninference. The old string-literal explicit-codec forms are still gone; these\nshape selections only apply to the schema-aware token-style macros.\n\nCurrent limitation: `params = Type` and `params = _` are not yet supported for\ntyped SQL statements that use optional placeholders (`$value?`) or toggle\ngroups (`(...)?`). Those statements must omit the `params` selection and keep\nthe default tuple-shaped parameter contract.\n\n## Choosing the right SQL surface\n\nUse the highest-level surface that still fits the statement:\n\n- **`query!` / `command!`** — default path for schema-aware typed SQL in the\n  supported subset\n- **`Query::raw` / `Command::raw`** — fallback for unsupported extended-protocol\n  SQL when you still want typed parameters, typed rows, prepare support, or\n  streaming\n- **`sql!`** — lower-level fragment builder for named-placeholder composition\n  and fragment nesting; useful, but secondary to schema-aware typed SQL\n- **`simple_query_raw`** — simple-protocol escape hatch for raw SQL strings,\n  especially multi-statement bootstrap/migration work or commands where you do\n  not need typed params/results. It does not participate in typed prepared or\n  streaming flows.\n\n## TLS\n\nTLS is opt-in at runtime and explicit in configuration:\n\n```rust,no_run\nuse babar::{Config, TlsMode};\n\nlet _cfg = Config::new(\"db.example.com\", 5432, \"app\", \"app\")\n    .password(\"secret\")\n    .tls_mode(TlsMode::Require);\n```\n\nWhen connecting by IP address, set `tls_server_name(\"db.example.com\")` so SNI and hostname verification still use the certificate's DNS name. For self-signed deployments, point `tls_root_cert_path(...)` at the CA PEM file. Over TLS, babar automatically upgrades SCRAM to `SCRAM-SHA-256-PLUS` when PostgreSQL offers channel binding.\n\n## Bulk ingest with COPY\n\n`babar` ships a dedicated typed API for **binary `COPY FROM STDIN`** bulk ingest. `#[derive(Codec)]` infers the common field codecs here; add `#[pg(codec = \"...\")]` only when you want a different mapping or inference does not apply:\n\n```rust,no_run\nuse babar::{CopyIn, Session};\nuse babar::query::Query;\nuse babar::Config;\n\n#[derive(Clone, Debug, PartialEq, babar::Codec)]\nstruct UserRow {\n    id: i32,\n    email: String,\n    note: Option\u003cString\u003e,\n    #[pg(codec = \"varchar\")]\n    handle: String,\n}\n\n# async fn demo() -\u003e babar::Result\u003c()\u003e {\nlet session = Session::connect(\n    Config::new(\"localhost\", 5432, \"postgres\", \"postgres\").password(\"secret\"),\n)\n.await?;\n\nsession\n    .simple_query_raw(\n        \"CREATE TEMP TABLE copy_users (id int4 PRIMARY KEY, email text NOT NULL, note text, handle varchar NOT NULL)\",\n    )\n    .await?;\n\nlet rows = vec![\n    UserRow { id: 1, email: \"ada@example.com\".into(), note: Some(\"first\".into()), handle: \"ada\".into() },\n    UserRow { id: 2, email: \"bob@example.com\".into(), note: None, handle: \"bob\".into() },\n];\n\nlet copy = CopyIn::binary(\n    \"COPY copy_users (id, email, note, handle) FROM STDIN BINARY\",\n    UserRow::CODEC,\n);\nsession.copy_in(\u0026copy, rows).await?;\n\nlet select: Query\u003c(), UserRow\u003e = Query::raw(\n    \"SELECT id, email, note, handle FROM copy_users ORDER BY id\",\n    (),\n    UserRow::CODEC,\n);\nassert_eq!(session.query(\u0026select, ()).await?.len(), 2);\nsession.close().await?;\n# Ok(())\n# }\n```\n\nThe COPY surface is intentionally limited to bulk ingest with binary `COPY FROM STDIN`. `COPY TO`, text COPY, and CSV COPY are not implemented.\n\n## Schema migrations\n\n`babar` ships a library-first migration engine plus a thin CLI example wrapper.\n\n- file names are paired as `\u003cversion\u003e__\u003cname\u003e.up.sql` and `\u003cversion\u003e__\u003cname\u003e.down.sql`\n- `version` is a `u64`; `name` must be lowercase `snake_case`\n- each migration must provide both files\n- scripts are transactional by default; opt out per file with `--! babar:transaction = none`\n- applied history lives in `public.babar_schema_migrations` by default\n\nUse the library API during startup before serving traffic:\n\n```rust,no_run\nuse babar::migration::FileSystemMigrationSource;\nuse babar::{Config, Migrator, Session};\n\n#[tokio::main(flavor = \"current_thread\")]\nasync fn main() -\u003e babar::Result\u003c()\u003e {\n    let session = Session::connect(\n        Config::new(\"localhost\", 5432, \"postgres\", \"app\").password(\"secret\"),\n    )\n    .await?;\n    let migrator = Migrator::new(FileSystemMigrationSource::new(\"migrations\"));\n    let plan = migrator.apply(\u0026session).await?;\n    println!(\"applied {} migration(s)\", plan.steps().len());\n    session.close().await?;\n    Ok(())\n}\n```\n\nThat startup path is safe to call from multiple processes: babar creates the\nstate table if needed, acquires a PostgreSQL advisory lock before changing\nstate, and treats re-running `apply` as a no-op once the applied prefix matches\ndisk.\n\nThe CLI example wraps the same engine:\n\n```text\ncargo run -p babar --example migration_cli -- status\ncargo run -p babar --example migration_cli -- plan\ncargo run -p babar --example migration_cli -- up\ncargo run -p babar --example migration_cli -- down --steps 1\n```\n\nKey operational rules:\n\n- `status`, `plan`, `up`, and `down` all enforce checksum and transaction-mode\n  drift detection for already-applied migrations\n- advisory locking only serializes babar migration runners that share the same\n  lock id; override it with `MigratorOptions` or `--migration-lock-id` only on\n  purpose\n- non-transactional scripts run outside an explicit transaction so PostgreSQL\n  features like `CREATE INDEX CONCURRENTLY` work, but partial effects may remain\n  if such a script fails\n- rollbacks only cover the currently applied prefix and only what the checked-in\n  `down` scripts can reverse; requesting more steps than are applied just rolls\n  back the whole applied prefix\n\n## Examples\n\nReal-world example programs live in `crates/core/examples/`:\n\n- `quickstart` — smallest typed end-to-end example\n- `derive_codec` — struct mapping with inferred `#[derive(Codec)]` defaults\n- `prepared_and_stream` — prepared statements plus streaming\n- `transactions` / `pool` — M4 lifecycle walkthroughs\n- `copy_bulk` — `Vec\u003cStruct\u003e` bulk ingest with `CopyIn\u003cT\u003e`\n- `migration_cli` — migration status/plan/apply/rollback wrapper over the shared engine\n- `todo_cli` — CLI app using `clap`\n- `axum_service` — small Axum JSON API backed by `Pool`\n\nRun one with:\n\n```text\ncargo run -p babar --example todo_cli -- --help\ncargo run -p babar --example axum_service\n```\n\n## Choosing a Rust Postgres tool\n\nDifferent Rust data-access libraries optimize for different trade-offs. `babar`\nis aimed at teams that want a Postgres-specific client with explicit typed query\nvalues, explicit codecs, and early validation around prepare-time schema drift.\n\n| If you care most about... | `babar` | `sqlx` | `tokio-postgres` |\n| --- | --- | --- | --- |\n| Database scope | Postgres only | Postgres, MySQL, SQLite, MSSQL | Postgres only |\n| Query model | Typed runtime `Query\u003cP, R\u003e` / `Command\u003cP\u003e` values | Raw SQL plus compile-time macros | Raw SQL strings plus codec traits |\n| Compile-time SQL checking | Optional, online-only macros | Strongest emphasis here, including offline workflows | Minimal |\n| Runtime explicitness | Very explicit codecs and row shapes | More macro- and trait-driven | More trait-driven |\n| Feature coverage / maturity today | Focused `0.1` surface | Broad ecosystem and tooling | Most battle-tested async Postgres driver in Rust |\n| Best fit | Postgres-specific apps that want explicit typed values | Teams prioritizing compile-time SQL workflows or multi-database support | Teams prioritizing mature Postgres coverage and established operational history |\n\nNone of those are \"wrong\" choices. If your team prefers compile-time SQL by\ndefault, `sqlx` is a strong fit. If you need the widest async Postgres feature\ncoverage today, `tokio-postgres` remains the reference point. If you want a\nsingle Postgres-focused API where query shape and codec shape stay visible in\nthe types, `babar` is designed for that workflow.\n\n## Status\n\n`babar` `0.2.0` is the next planned release on this branch, building on the\nalready-published `0.1.0` crates. The book is published via GitHub Pages at\n[`https://babar.notthatbreezy.io`](https://babar.notthatbreezy.io), and\n`.internal/RELEASE.md` remains the maintenance runbook for future releases.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnotthatbreezy%2Fbabar","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnotthatbreezy%2Fbabar","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnotthatbreezy%2Fbabar/lists"}