From 85b05523b9dc4df412df9d4e4832e820c69ced47 Mon Sep 17 00:00:00 2001 From: Tam Nguyen Duc <1218621+tamnd@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:26:02 +0700 Subject: [PATCH] the sixty second snippet, compiled and run as it is printed A quickstart is the most read and least compiled code a project has. It lives in a README, it is copied by hand, and it goes wrong quietly, a rename at a time, until a reader's first five minutes go on an error message instead of on a database. So the snippet is a program. crates/zu-snippets holds it as an example and a test holds the README to it character for character, then runs it in a directory of its own and checks what it printed. Compiling proves the names still exist; running proves the statements still parse, the file still opens and the two lines on the page are the two lines it prints. The package exists because a crate cannot depend on itself under a second name, and the snippet has to say `zudb`, which is what a reader types after `cargo add zudb` and not what this crate is called in this workspace. crates/zu/benches/convert.rs is reformatted, which is nothing to do with any of this and is what `cargo fmt --all --check` wants. --- README.md | 29 +++++ crates/zu-snippets/Cargo.toml | 26 +++++ crates/zu-snippets/examples/sixty-seconds.rs | 19 ++++ crates/zu-snippets/src/lib.rs | 25 ++++ crates/zu-snippets/tests/readme.rs | 113 +++++++++++++++++++ crates/zu/benches/convert.rs | 5 +- 6 files changed, 213 insertions(+), 4 deletions(-) create mode 100644 crates/zu-snippets/Cargo.toml create mode 100644 crates/zu-snippets/examples/sixty-seconds.rs create mode 100644 crates/zu-snippets/src/lib.rs create mode 100644 crates/zu-snippets/tests/readme.rs diff --git a/README.md b/README.md index 3fa39116..f6b43083 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,34 @@ It is columnar, vectorized, and factorized in the DuckDB and Kùzu mold, with on - `sqlite` stores the graph in an ordinary SQLite database file, for interop and as the differential-testing oracle. - `s3` is object-storage-native with immutable segments, compare-and-swap manifest commits, and a request accountant that keeps the monthly bill flat. +## Sixty seconds + +```rust +use zudb::{Database, params}; + +fn main() -> zudb::Result<()> { + let db = Database::create("social.zu1")?; + let mut conn = db.connect()?; + + conn.execute("INSERT (p:person {uid: 1, name: 'ada'})")?; + conn.execute("INSERT (p:person {uid: 2, name: 'grace'})")?; + + let rows = conn.query_with( + "MATCH (p:person) WHERE p.uid >= $uid RETURN p.name AS name, p.uid AS uid", + ¶ms! { "uid" => 1 }, + )?; + for row in rows.iter() { + let (name, uid): (&str, i64) = row.get()?; + println!("{name} {uid}"); + } + Ok(()) +} +``` + +That is `cargo add zudb` and the whole program: no server, no schema step, no cluster. `create` makes the file and `open` is what you use the second time, because a create that found a database and opened it instead is the call that quietly writes into somebody else's data. The same sixty seconds in Python, `import zudb`, `zudb.connect`, `.to_pandas()`, is in [zu-python](https://github.com/tamnd/zu-python). + +The snippet above is a program in this repository, `crates/zu-snippets/examples/sixty-seconds.rs`, and a test holds this README to it character for character and then runs it. A quickstart is the most read and least compiled code a project has, which is how it comes to be wrong. + ## Status Early. The specification is complete and lives in [docs/](docs/), starting with the [overview](docs/00-overview.md). @@ -38,6 +66,7 @@ crates/zu-s3 object-storage engine crates/zu-query parser, planner, factorized executor crates/zu the public embedded API (published as zudb) crates/zu-cli the zu binary +crates/zu-snippets the snippets this README prints, compiled and run docs/ the specification, byte-level where it matters ``` diff --git a/crates/zu-snippets/Cargo.toml b/crates/zu-snippets/Cargo.toml new file mode 100644 index 00000000..c348e5df --- /dev/null +++ b/crates/zu-snippets/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "zu-snippets" +description = "The snippets this repository publishes, compiled and run as printed" +publish = false +version.workspace = true +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +authors.workspace = true + +[lib] +path = "src/lib.rs" + +[dependencies] +# Under the name it is published as, which is the name a reader types +# after `cargo add zudb` and therefore the name the printed snippet has +# to use. A crate cannot depend on itself under another name, which is +# the whole reason the snippets are a package of their own. +zudb = { package = "zu", path = "../zu" } + +[dev-dependencies] +tempfile.workspace = true + +[lints] +workspace = true diff --git a/crates/zu-snippets/examples/sixty-seconds.rs b/crates/zu-snippets/examples/sixty-seconds.rs new file mode 100644 index 00000000..f6a78fec --- /dev/null +++ b/crates/zu-snippets/examples/sixty-seconds.rs @@ -0,0 +1,19 @@ +use zudb::{Database, params}; + +fn main() -> zudb::Result<()> { + let db = Database::create("social.zu1")?; + let mut conn = db.connect()?; + + conn.execute("INSERT (p:person {uid: 1, name: 'ada'})")?; + conn.execute("INSERT (p:person {uid: 2, name: 'grace'})")?; + + let rows = conn.query_with( + "MATCH (p:person) WHERE p.uid >= $uid RETURN p.name AS name, p.uid AS uid", + ¶ms! { "uid" => 1 }, + )?; + for row in rows.iter() { + let (name, uid): (&str, i64) = row.get()?; + println!("{name} {uid}"); + } + Ok(()) +} diff --git a/crates/zu-snippets/src/lib.rs b/crates/zu-snippets/src/lib.rs new file mode 100644 index 00000000..72fb305d --- /dev/null +++ b/crates/zu-snippets/src/lib.rs @@ -0,0 +1,25 @@ +//! The snippets this repository publishes, as programs. +//! +//! A quickstart snippet is read far more often than any other code the +//! project ships, and it is the one piece of code nothing compiles: it +//! lives in a README, it is copied by hand, and it goes wrong quietly, +//! a rename at a time, until a reader's first five minutes are spent +//! on an error message. So the snippets are programs here, one per +//! `examples/`, and `tests/readme.rs` holds the README to them +//! character for character and then runs them. +//! +//! Running is the half that matters. A snippet that compiles proves +//! the names still exist; a snippet that runs proves the statements in +//! it still parse, the file it writes still opens, and the numbers it +//! prints are the numbers the page claims. Each one runs in a +//! directory of its own, because it writes a database into the working +//! directory exactly as a reader's copy would. +//! +//! The package exists at all because a crate cannot depend on itself +//! under a second name. This one is published as `zudb`, which is what +//! a reader types, and no example inside it could say `zudb` while +//! being an example of the same package. + +/// The examples this package holds, in the order the README prints +/// them, and the name of the fenced block each one has to match. +pub const SNIPPETS: &[&str] = &["sixty-seconds"]; diff --git a/crates/zu-snippets/tests/readme.rs b/crates/zu-snippets/tests/readme.rs new file mode 100644 index 00000000..e66d3776 --- /dev/null +++ b/crates/zu-snippets/tests/readme.rs @@ -0,0 +1,113 @@ +//! The README against the programs it prints. +//! +//! Two checks, and the second is the one with teeth. The text in the +//! fenced block has to be the text of the example, character for +//! character, so a snippet cannot be edited on the page into something +//! that was never compiled. Then the example runs, in a directory of +//! its own, and has to print what the page says it prints, so a +//! snippet cannot go on compiling after the statements in it stopped +//! meaning what they meant. + +use std::path::{Path, PathBuf}; +use std::process::Command; + +/// The repository root, two levels above this package. +fn root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .ancestors() + .nth(2) + .expect("the package sits two levels under the root") + .to_path_buf() +} + +/// Every fenced block of one language in a markdown file, in order. +fn blocks(markdown: &str, language: &str) -> Vec { + let mut found = Vec::new(); + let mut lines = markdown.lines(); + while let Some(line) = lines.next() { + if line.trim_end() != format!("```{language}") { + continue; + } + let mut block = String::new(); + for line in lines.by_ref() { + if line.trim_end() == "```" { + break; + } + block.push_str(line); + block.push('\n'); + } + found.push(block); + } + found +} + +/// Where the example binary is, building it if this test was run in a +/// way that did not. `cargo test` builds examples and `cargo test +/// --test readme` does not, and a test that only passes under one of +/// them is a test somebody will disbelieve. +fn example(name: &str) -> PathBuf { + // The test binary is in target//deps, and an example of + // the same build is in target//examples. + let exe = std::env::current_exe().expect("this test has a path"); + let built = exe + .parent() + .and_then(Path::parent) + .expect("target//deps/") + .join("examples") + .join(name); + let built = built.with_extension(std::env::consts::EXE_EXTENSION); + if built.exists() { + return built; + } + let status = Command::new(env!("CARGO")) + .args(["build", "-p", "zu-snippets", "--example", name]) + .current_dir(root()) + .status() + .expect("cargo runs"); + assert!(status.success(), "building the {name} example"); + assert!(built.exists(), "no {name} example at {}", built.display()); + built +} + +#[test] +fn the_readme_prints_the_program_this_repository_compiles() { + let readme = std::fs::read_to_string(root().join("README.md")).expect("a README"); + let printed = blocks(&readme, "rust"); + assert_eq!( + printed.len(), + zu_snippets::SNIPPETS.len(), + "the README prints {} Rust blocks and this package holds {} snippets", + printed.len(), + zu_snippets::SNIPPETS.len() + ); + for (block, name) in printed.iter().zip(zu_snippets::SNIPPETS) { + let path = Path::new(env!("CARGO_MANIFEST_DIR")) + .join("examples") + .join(format!("{name}.rs")); + let source = std::fs::read_to_string(&path).expect("the example is there"); + assert_eq!( + block, + &source, + "the README block and {} have drifted apart", + path.display() + ); + } +} + +#[test] +fn the_sixty_second_program_runs_and_prints_what_the_readme_says() { + let dir = tempfile::tempdir().expect("a directory of its own"); + let run = Command::new(example("sixty-seconds")) + .current_dir(dir.path()) + .output() + .expect("the example runs"); + assert!( + run.status.success(), + "the example failed: {}", + String::from_utf8_lossy(&run.stderr) + ); + assert_eq!(String::from_utf8_lossy(&run.stdout), "ada 1\ngrace 2\n"); + // The reader's copy writes into the directory they ran it from, + // which is the part of the story a compile check cannot see. + assert!(dir.path().join("social.zu1").is_file()); +} diff --git a/crates/zu/benches/convert.rs b/crates/zu/benches/convert.rs index e1dff2f5..3dba3114 100644 --- a/crates/zu/benches/convert.rs +++ b/crates/zu/benches/convert.rs @@ -68,10 +68,7 @@ fn stage(path: &std::path::Path) { sq.insert_node_at( "person", row, - &[ - SqlValue::Int(row), - SqlValue::Text(format!("person-{row}")), - ], + &[SqlValue::Int(row), SqlValue::Text(format!("person-{row}"))], ) .expect("node"); }