Welds ORM

repository·main·Indexed 42 days ago

https://github.com/weldsorm/welds

An asynchronous ORM for Rust supporting Postgres, MSSQL, MySQL, and SQLite. Welds provides a rich DSL for querying and model management using the `#[derive(WeldsModel)]` macro, avoiding complex trait abstractions. It includes support for relationships via `BelongsTo`, asynchronous connections, and a `Client` trait for unified database interaction. Version 0.5.1.

Tokens
26.1K
Snippets
124
Records
140
Agent score
53%

What's inside Welds

  1. Connect to a database with welds::connections

    main

    Use welds::connections::connect(url) to establish an asynchronous connection to your database. The connection URL format depends on your database driver (e.g., postgres://...).

    let url = "postgres://postgres:password@localhost:5432";
    let client = welds::connections::connect(url).await.unwrap();
  2. Define a Welds model using macros

    main

    To use Welds, define your data models as Rust structs and decorate them with the #[derive(WeldsModel)] macro. You can use the #[welds(...)] attribute to specify the schema, table name, primary keys, column renames, and relationships like BelongsTo.

    Key attributes:

    • #[welds(schema = "...")]: Specifies the database schema.
    • #[welds(table = "...")]: Specifies the database table name.
    • #[welds(primary_key)]: Marks a field as the primary key.
    • #[welds(rename = "...")]: Maps a struct field to a different database column name.
    • #[welds(BelongsTo(relation_name, target_type, foreign_key))]: Defines a relationship to another model.
    #[derive(Debug, WeldsModel)]
    #[welds(schema= "inventory", table = "products")]
    #[welds(BelongsTo(seller, super::people::People, "seller_id"))]
    pub struct Product {
        #[welds(rename = "product_id")]
        #[welds(primary_key)]
        pub id: i32,
        pub name: String,
        pub seller_id: Option<i32>,
        pub description: Option<String>,
        pub price: Option<f32>,
    }
  3. Generate Rust code from a database using welds-cli

    main

    To generate Rust struct definitions for your database tables using welds-cli, follow these steps:

    1. Set the DATABASE_URL environment variable to your database connection string.
    2. Run welds update to create a welds.yaml database definition file.
    3. Run welds generate to produce the Rust files for your tables.
    # 1. Set connection string
    export DATABASE_URL=postgres://postgres:password@localhost:5432
    
    # 2. Create welds.yaml definition
    welds update
    
    # 3. Generate rust code
    welds generate
  4. Enable external types (chrono, serde_json) for SQLX and Tiberius

    main

    Welds allows using types from external crates like chrono or serde_json, but you must enable the corresponding features in the underlying drivers (sqlx or Tiberius).

    For SQLX (Postgres, MySQL, Sqlite):

    1. Add the external crate: cargo add chrono
    2. Enable the feature in sqlx: cargo add sqlx --features=chrono

    For Tiberius (MSSQL):

    1. Add the external crate: cargo add chrono
    2. Enable the feature in welds-connections: cargo add welds-connections --features=mssql,mssql-chrono

    Available welds-connections features for MSSQL:

    • mssql-chrono
    • mssql-time
    • mssql-rust_decimal
    • mssql-bigdecimal
  5. Define model relationships with #[welds(...)]

    main

    Relationships between models are defined using the #[welds(...)] attribute on the struct. Supported relationship types include:

    • HasMany
    • BelongsTo
    • HasOne
    • ManualRelationship
    • JoinTable (for many-to-many relationships)
    #[welds(HasMany(posts), BelongsTo(user))]
    struct Post {
        // ...
    }
  6. Use AnyClient for database-agnostic operations

    main

    The AnyClient enum acts as a unified wrapper around various database connections. It allows you to write code that works across different database backends (like Postgres, MySQL, SQLite, or MSSQL) by implementing the Client trait. The specific available backends depend on the cargo features enabled in your project.

    Available variants (based on enabled features):

    • Sqlite(crate::sqlite::SqliteClient)
    • SqliteSync(crate::sqlite_sync::SqliteClient)
    • Postgres(crate::postgres::PostgresClient)
    • Mysql(crate::mysql::MysqlClient)
    • Mssql(crate::mssql::MssqlClient)
    • Noop(crate::noop::NoopClient)
  7. Use QueryBuilder to construct queries

    main

    The QueryBuilder<T> is a fluent API used to build un-executed SQL queries that can be run against a database. It can be chained to add filters, ordering, limits, and joins. You typically start a query using a model's static methods (e.g., Thing::all()).

    use welds::prelude::*;
    
    // Starting a query for the 'Thing' model
    let query = Thing::all()
        .where_col(|c| c.id.gt(10))
        .limit(5)
        .order_by_asc(|c| c.id);
    
    // Executing the query
    let rows = query.run(db).await?;
  8. Implement model lifecycle hooks

    main

    Welds allows you to define lifecycle hooks that trigger automatically during database operations (Create, Update, Delete). These hooks are implemented by providing callback functions that the welds procedural macros will automatically wire into the model's trait implementations.

    Available Hook Types

    Hook KindTrait ImplementedMethodReceiverBehavior
    BeforeCreateBeforeCreatebefore(&mut self)&mut selfCan return Result<()> to abort the creation.
    AfterCreateAfterCreateafter(&self)&selfRuns after creation; cannot abort the operation.
    BeforeUpdateBeforeUpdatebefore(&mut self)&mut selfCan return Result<()> to abort the update.
    AfterUpdateAfterUpdateafter(&self)&selfRuns after update; cannot abort the operation.
    BeforeDeleteBeforeDeletebefore(&self)&selfCan return Result<()> to abort the deletion.
    AfterDeleteAfterDeleteafter(&self)&selfRuns after deletion; cannot abort the operation.

    Key Characteristics

    • Async Support: Hooks can be defined as async functions. The macro handles the .await calls automatically.
    • Error Handling: Before hooks return Result<()>. If a Before hook returns an Err, the corresponding database operation is aborted.
    • Read-only Models: If a model is marked as readonly, the macro will not implement any save hooks (Create, Update, or Delete).
    • Receiver Types: Note that Before hooks for Create and Update receive &mut self to allow modifying the model before it hits the database, while After hooks and BeforeDelete receive &self.
  9. Understand the generated HasRelations implementation

    main

    When you define relations on a model, Welds automatically implements the HasRelations trait for your struct. This implementation enables lambda-based selection of relationships.

    It generates a companion struct (the name of which is provided in your configuration) that contains fields for each relationship. These fields use specific relationship types from the welds::relations module, such as BelongsTo<Model, OtherModel>, HasMany<Model, OtherModel>, or HasOne<Model, OtherModel>.

  10. Generated Model Structure for Welds

    main

    When using the welds-cli to generate models from a database schema, the tool produces a Rust struct for each table. These structs are decorated with the #[derive(Debug, WeldsModel)] macro and use #[welds(...)] attributes to map database columns and relations to Rust fields.

    Key characteristics of generated models:

    • Table Mapping: Uses #[welds(schema = "...", table = "table_name")] to link the struct to its database source.
    • Field Attributes:
      • #[welds(primary_key)]: Marks the primary key column.
      • #[welds(readonly)]: Marks columns that are not writeable.
      • #[welds(rename = "db_name")]: Used if the Rust model field name differs from the database column name.
    • Nullability: Columns marked as nullable in the database are wrapped in Option<T> in Rust.
    • Relations: Relationships (HasMany, HasOne, BelongsTo) are implemented as attributes on the struct, allowing navigation between related models.
    use welds::WeldsModel;
    
    #[derive(Debug, WeldsModel)]
    #[welds(schema = "public", table = "users")]
    #[welds(BelongsTo(auth, super::super::auth::User, user_id))]
    pub struct User {
        #[welds(primary_key)]
        pub id: i32,
        #[welds(rename = "email_address")]
        pub email: String,
        pub bio: Option<String>,
    }