SeaQuery

repository·master·Indexed 23 days ago

https://github.com/seaql/sea-query

A dynamic SQL query builder for Rust supporting MySQL, Postgres, and SQLite. SeaQuery allows developers to construct complex queries, expressions, and schemas as abstract syntax trees, providing cross-database compatibility and safe parameter binding. It features a fluent interface with conditional clause appending via `apply_if`, support for complex logical grouping with `Cond`, and a `raw_query!` macro for improved raw SQL ergonomics. It integrates with drivers such as SQLx, Diesel, and Rusqlite.

Tokens
30.4K
Snippets
98
Records
152
Agent score
82%

What's inside sea-query

  1. Implement identifiers using the Iden trait

    master

    The Iden trait is used for identifiers (table names, column names, etc.) in any query statement. You can implement it manually or use the #[derive(Iden)] macro on enums or structs to automatically convert variants/fields into string identifiers.

    Additionally, the #[enum_def] macro can be used to generate an associated Iden enum for a struct.

    #[derive(Iden)]
    enum Character {
        Table,
        Id,
        FontId,
        FontSize,
    }
    
    assert_eq!(Character::Table.to_string(), "character");
    assert_eq!(Character::FontId.to_string(), "font_id");
    
    #[derive(Iden)]
    struct Glyph;
    assert_eq!(Glyph.to_string(), "glyph");
  2. Serialize statements using QueryBuilder and SchemaBuilder

    master

    Statements are categorized into Query and Schema statements.

    Query Statements

    • build<T: QueryBuilder>(&self, query_builder: T) -> (String, Values): Returns the SQL string and the parameter values. This is the preferred method for production as it uses the database driver's binary protocol, offering better security and performance.
    • to_string<T: QueryBuilder>(&self, query_builder: T) -> String: Returns the SQL string with parameters injected. Use this for testing and debugging.

    Schema Statements

    • build<T: SchemaBuilder>(&self, schema_builder: T) -> String: Serializes schema definitions (like CREATE TABLE) into SQL strings.
  3. Build complex conditions with `Cond`

    master

    SeaQuery uses an internal Abstract Syntax Tree (AST) to represent conditions. You can compose complex logic using Cond::any() (OR) and Cond::all() (AND). SeaQuery automatically manages parentheses and respects operator precedence to ensure valid SQL.

    assert_eq!(
        Query::select()
            .column("id")
            .from("glyph")
            .cond_where(
                Cond::any()
                    .add(
                        Cond::all()
                            .add(Expr::col("aspect").is_null())
                            .add(Expr::col("image").is_null())
                    )
                    .add(
                        Cond::all()
                            .add(Expr::col("aspect").is_in([3, 4]))
                            .add(Expr::col("image").like("A%"))
                    )
            )
            .to_string(PostgresQueryBuilder),
        [ 
            r#"SELECT "id" FROM "glyph""#,
            r#"WHERE"#,
            r#"("aspect" IS NULL AND "image" IS NULL)"#,
            r#"OR"#,
            r#"("aspect" IN (3, 4) AND "image" LIKE 'A%')"#,
        ]
        .join(" ")
    );
  4. Handle parameter bindings automatically

    master

    SeaQuery handles the sequencing of parameters (e.g., $1, $2 or ?) automatically. When you use expressions or custom values, SeaQuery ensures the correct order of values is maintained in the resulting Values object, preventing "off by one" errors common in raw SQL.

    assert_eq!(
        Query::select()
            .expr(Expr::col("size_w").add(1).mul(2))
            .from("glyph")
            .and_where(Expr::col("image").like("A"))
            .and_where(Expr::col("id").is_in([3, 4, 5]))
            .build(PostgresQueryBuilder),
        (
            r#"SELECT ("size_w" + $1) * $2 FROM "glyph" WHERE "image" LIKE $3 AND "id" IN ($4, $5, $6)"#
                .to_owned(),
            Values(vec![
                1.into(),
                2.into(),
                "A".to_owned().into(),
                3.into(),
                4.into(),
                5.into(),
            ])
        )
    );
  5. Use table partitioning in CREATE TABLE

    master

    SeaQuery supports table partitioning for both PostgreSQL and MySQL via TableCreateStatement::add_partition.

    PostgreSQL supported partitioning:

    • PARTITION BY RANGE
    • PARTITION BY LIST
    • PARTITION BY HASH
    • PARTITION OF
    • FOR VALUES IN
    • FOR VALUES FROM ... TO
    • FOR VALUES WITH

    MySQL supported partitioning:

    • PARTITION BY RANGE
    • PARTITION BY LIST
    • PARTITION BY HASH
    • PARTITION BY KEY
    • Partition definitions with VALUES IN and VALUES LESS THAN
  6. Handle MySQL partial index limitations

    master

    MySQL does not support partial indexes. In version 1.0.0-rc.34, if IndexCreateStatement::cond_where is used with MysqlQueryBuilder, SeaQuery will now emit the WHERE clause instead of silently dropping it. This causes the database to reject the statement, which is preferred over creating an index with different semantics.

    Recommendation: If your application requires partial indexes, you should branch your logic by backend to ensure you only attempt to create partial indexes on supported databases like PostgreSQL or SQLite.

  7. Run the SeaQuery Diesel Postgres example

    master

    To run the Diesel Postgres integration example, navigate to the example directory and use cargo run.

    This example demonstrates common database operations including:

    • Creating tables
    • Inserting records
    • Selecting single and multiple records
    • Updating records
    • Counting records
    • Upserting records
    • Deleting records
    • Handling complex types like UUID, JSON (Object), Decimal, BigDecimal, DateTime, Inet, MacAddress, and Boolean arrays.
    cargo run
  8. Install SeaQuery

    master

    Add sea-query to your Cargo.toml dependencies. The library is lightweight and all dependencies are optional, so you should enable only the features you need.

    # Cargo.toml
    [dependencies]
    sea-query = "1.0"
  9. Construct dynamic queries with `apply_if`

    master

    You can build queries at runtime using a fluent interface. The apply_if method allows you to conditionally append clauses (like WHERE or AND) based on the presence of an Option value, avoiding manual string concatenation or complex conditional logic.

    fn query(a: Option<i32>, b: Option<char>) -> SelectStatement {
        Query::select()
            .column("id")
            .from("character")
            .apply_if(a, |q, v| {
                q.and_where(Expr::col("font_id").eq(v));
            })
            .apply_if(b, |q, v| {
                q.and_where(Expr::col("ascii").like(v));
            })
            .take()
    }
  10. Use stable companion crate versions with sea-query 1.0.0

    master

    If you are using the stable sea-query 1.0.0 release, ensure your companion crates are aligned to the following versions to maintain compatibility:

    sea-query = "1.0"
    sea-query-derive = "1.0"
    sea-query-rusqlite = "0.8"
    sea-query-postgres = "0.6"
    sea-query-diesel = "0.3"
    sea-query-rbatis = "0.2"
  11. Select the correct sea-query-sqlx version for your SQLx runtime

    master

    When using the SQLx binder, you must match the sea-query-sqlx version to your project's sqlx version.

    • For SQLx 0.8: Use sea-query-sqlx = "0.8.1".
    • For SQLx 0.9: Use sea-query-sqlx = "0.9".

    Note: sea-query-sqlx 0.8.0 has been yanked; always use 0.8.1 for SQLx 0.8 compatibility.

    # SQLx 0.8 compatibility line
    sea-query-sqlx = "0.8.1"
    
    # SQLx 0.9 line
    sea-query-sqlx = "0.9"