Skip to main content

Crate drizzle_seed

Crate drizzle_seed 

Source
Expand description

Deterministic test data for drizzle-rs schemas.

SeedConfig turns a schema into INSERT statements, or into plain rows with try_generate_rows for use with any driver. The same seed and crate version always give the same rows.

How values are chosen, per column:

  1. a Generator set with .generator(...): one from generators (generators::int(18..=90), generators::one_of([...]), generators::from_fn(...), …), a GeneratorKind, or your own type; else

  2. a GeneratorKind set with .kind(...); else

  3. DEFAULT when the column has a default (and is not the primary key), or is a non-key PostgreSQL identity column; else

  4. an inferred generator:

    • integer primary keys count up from 1;
    • enum columns pick one of their variants;
    • MySQL columns follow their declared domain (integer ranges, inline ENUM/SET labels, DECIMAL precision, …);
    • otherwise the column name decides when a whole word is recognized (email, first_name, created_at, is_active, …) and the generated values fit the column type, then the SQL type alone.

    Text is cut to a declared VARCHAR(n)/CHAR(n) length.

UNIQUE columns and single-column primary keys get distinct values, and a row that would repeat a composite primary key or multi-column UNIQUE key (for example two equal (user_id, post_id) pairs in a join table) is dropped. Parent tables are seeded before their children, and foreign key columns are overwritten to point at generated parent rows. A child table without its own count gets parent rows × relation count rows (the relation count defaults to 1; with several parents, the largest product wins). reset_plan returns DELETE statements in child-before-parent order.

On PostgreSQL, text values for non-text columns (uuid, jsonb, enums, arrays, …) are cast to the column type, GENERATED ALWAYS identity keys are inserted with OVERRIDING SYSTEM VALUE, and each table’s SERIAL/IDENTITY sequences are moved past the seeded ids with a SELECT setval(...) statement after its rows.

The crate has no default dialect: enable sqlite, postgres, and/or mysql.

§With the schema macros

This is the main path: pass the #[derive(...Schema)] struct. The macros already record column types, keys, UNIQUE, defaults and enum variants, and every table or column passed to the config is checked at compile time. (Uses drizzle with the rusqlite feature; not compiled here because drizzle is not a dependency of this crate.)

use drizzle::sqlite::prelude::*;
use drizzle_seed::{SeedConfig, generators::{self, GeneratorExt}};

#[SQLiteTable]
struct Users {
    #[column(primary)]
    id: i32,
    #[column(unique)]
    email: String,    // inferred from the name: distinct emails
    age: i32,
}

#[SQLiteTable]
struct Posts {
    #[column(primary)]
    id: i32,
    #[column(references = Users::id)]
    user_id: i32,     // points at seeded users
    title: String,    // inferred from the name: a short title
}

#[derive(SQLiteSchema)]
struct AppSchema {
    users: Users,
    posts: Posts,
}

let schema = AppSchema::new();
let statements = SeedConfig::sqlite(&schema)
    .seed(42)
    .count(&schema.users, 5)                     // 5 users
    .relation(&schema.users, &schema.posts, 3)   // 3 posts per user: 15 posts
    .generator(&schema.users.age, generators::int(18..=90).nullable(0.1))
    .generate();

for statement in statements {
    db.execute(statement)?; // parents first
}

§Without the schema macros

Describe the existing tables with schema::Schema, then use the *_by_name settings. Names are checked when the seed is generated, and try_generate_rows gives plain rows for any driver:

use drizzle_seed::schema::{Column, Schema, Table};
use drizzle_seed::{SeedConfig, SeedError};

let schema = Schema::postgres()
    .table(
        Table::new("users")
            .column(Column::new("id", "BIGSERIAL").primary_key())
            .column(Column::new("email", "TEXT").not_null().unique()),
    )
    .table(
        Table::new("posts")
            .column(Column::new("id", "BIGSERIAL").primary_key())
            .column(Column::new("user_id", "BIGINT").not_null().references("users", "id"))
            .column(Column::new("title", "TEXT").not_null()),
    );

let config = SeedConfig::postgres(&schema)
    .count_by_name("users", 5)
    .relation_by_name("users", "posts", 3);
for table in config.try_generate_rows()? {
    // INSERT INTO {table.table} ({table.columns}) VALUES ... for each row
    assert_eq!(table.rows.len(), if table.table == "users" { 5 } else { 15 });
}

// Execute the reset plan, children first, to empty the tables again.
let reset = config.reset_plan()?;
assert_eq!(reset.len(), 2);

// A typo is an error, which lists the names that do exist.
let error = SeedConfig::postgres(&schema).count_by_name("user", 5).try_generate();
assert!(matches!(error, Err(SeedError::UnknownTable { .. })));

§From a live database or a migration snapshot

Schema::from_snapshot (the migrations feature) builds the schema from what a drizzle driver’s introspect() reads, or from a migration folder’s snapshot.json, so a database described nowhere in Rust can be seeded (on PostgreSQL, introspect_schemas(&["app"]) reads only the schemas named). The drizzle seed CLI command does this for the configured database.

use drizzle_seed::{SeedConfig, schema::Schema};

let schema = Schema::from_snapshot(&db.introspect()?)?;
for statement in SeedConfig::postgres(&schema)
    .count_by_name("users", 100)
    .relation_by_name("users", "posts", 3)
    .generate()
{
    db.execute(statement)?;
}

§As a SQL script

Each statement’s inline_sql() writes its values as literals, and SeedConfig::try_generate_script returns the whole seed as one script: a fixture file, or input for any client.

Any other type that implements drizzle_core::SQLSchemaImpl works as a schema too.

Modules§

generators
Ready-made generators to pass to SeedConfig::generator.
schema
Describe tables at runtime, for projects without the drizzle schema macros.

Structs§

SeedConfig
Builder that generates seed INSERT statements for a schema.
SeedRows
The generated rows for one table, before any SQL is rendered.

Enums§

GeneratorKind
A built-in generator, chosen by type/name inference or set with SeedConfig::kind.
SeedError
Why SeedConfig could not build the statements (returned by try_generate and reset_plan).
SeedValue
A generated column value, before it is rendered for a dialect.

Traits§

Generator
Produces one column value per row.
Rng
Re-export of rand::Rng, for drawing values from the RNG a Generator receives (rng.random_range(..), rng.random_bool(..)). User-level interface for RNGs
RngCore
Re-export of rand::RngCore, the RNG passed to Generator::generate. Implementation-level interface for RNGs