kx-sql (0.1.3)

Published 2026-01-14 17:48:33 +08:00 by caisin

Installation

[registry]
default = "gitea"

[registries.gitea]
index = "sparse+" # Sparse index
# index = "" # Git

[net]
git-fetch-with-cli = true
cargo add kx-sql@0.1.3

About this package

KX-SQL

A flexible and type-safe SQL query builder for Rust that supports multiple database backends.

Features

  • Type-safe query building: Compile-time guarantees for SQL correctness
  • Multiple database support: MySQL, Databend, and extensible for others
  • Fluent API: Chain methods for readable query construction
  • Macro support: Convenient macros for common operations
  • Comprehensive SQL support: SELECT, INSERT, UPDATE, DELETE, CREATE TABLE
  • Zero runtime overhead: All SQL generation happens at compile time

Quick Start

Add this to your Cargo.toml:

[dependencies]
kx-sql = "0.1"

Basic Usage

use kx_sql::*;

// Create a SELECT query
let mut query = SelectQuery::new("users");
query.add_column("id")
     .add_column("name")
     .where_clause(WhereClause::equals("active", true))
     .limit(10);

// Build MySQL query
let mysql_sql = query.to_mysql()?;
println!("{}", mysql_sql);
// Output: SELECT `id`, `name` FROM `users` WHERE `active` = ? LIMIT 10

Using Macros

use kx_sql::*;

// Create fields using macros
let id_field = field!("id", "int", pk);
let name_field = field!("name", "varchar", size: 50);
let email_field = field!("email", "varchar", not_null);

// Create table using macro
let table = table!("users", "User table", {
    fields: [id_field, name_field, email_field],
    indexes: [
        index!("user_email_idx", "email", unique)
    ],
    drop: true,
});

let create_sql = table.to_mysql()?;

Where Clauses

use kx_sql::*;

// Using macro helpers
let condition = where_equals!("status", "active")
    .and(where_like!("email", "%@company.com"))
    .or(where_in!("role", vec!["admin", "user"]));

let mut query = SelectQuery::new("users");
query.where_clause(condition);

Supported Operations

SELECT Queries

let mut query = SelectQuery::new("users");
query.alias("u")
     .columns(&["id", "name", "email"])
     .left_join("profiles", "p", "u.id = p.user_id")
     .where_clause(where_equals!("u.active", true))
     .group_by_str("u.department")
     .having(where_like!("COUNT(*)", "> 5"))
     .order_by_columns("u.created_at desc")
     .paginate(1, 20); // page 1, 20 items per page

let sql = query.to_mysql()?;

INSERT Queries

// Single insert
let mut insert = InsertQuery::into("users");
insert.fields(&["name", "email"])
      .values(Values::Data(vec![
          Record(vec!["John Doe".into(), "john@example.com".into()])
      ]));

// Batch insert with conflict resolution
let mut batch_insert = InsertQuery::into("users");
batch_insert.fields(&["name", "email"])
           .values(Values::Data(vec![
               Record(vec!["John".into(), "john@example.com".into()]),
               Record(vec!["Jane".into(), "jane@example.com".into()]),
           ]))
           .conflict(&Conflict::new().values("name,email"));

UPDATE Queries

let mut update = UpdateQuery::new("users");
update.set_field("last_login", "NOW()")
      .set_field("login_count", "login_count + 1")
      .where_clause(where_equals!("id", 123));

DELETE Queries

let delete = DeleteQuery::new("users")
    .where_clause(
        where_equals!("active", false)
            .and(where_like!("created_at", "< DATE_SUB(NOW(), INTERVAL 1 YEAR)"))
    );

CREATE TABLE

let mut table = CreateQuery::new("users");
table.comment("User management table")
     .field(&Field::id_field("id"))  // Auto-increment PK
     .varchar_field("username", 50)   // VARCHAR(50)
     .varchar_field("email", 100)     // VARCHAR(100)
     .timestamp_field("created_at")   // TIMESTAMP with CURRENT_TIMESTAMP
     .idx(&index!("username_idx", "username", unique));

let create_sql = table.to_mysql()?;

Database Support

MySQL

use kx_sql::mysql::BuildMySql;

let query = SelectQuery::new("users");
let sql = query.to_mysql()?;                    // Returns String
let (sql, params) = query.to_mysql_v()?;       // Returns (String, Vec<Value>)

Databend

use kx_sql::databend::BuildDatabend;

let query = SelectQuery::new("users");
let sql = query.to_databend()?;

Adding New Database Support

Implement the required traits:

use kx_sql::traits::*;

pub struct MyDatabaseBuilder;

impl CommonBuilder for MyDatabaseBuilder {
    fn quote(&self) -> &'static str {
        "\""  // Use double quotes instead of backticks
    }
}

impl SqlBuilder for MyDatabaseBuilder {}
impl ColumnBuilder for MyDatabaseBuilder {}
impl TableBuilder for MyDatabaseBuilder {}

Advanced Features

Complex Queries

// Subqueries
let subquery = SelectQuery::new("orders")
    .add_column("customer_id")
    .where_clause(where_like!("total", "> 1000"));

let mut main_query = SelectQuery::new("customers");
main_query.where_clause(
    where_in!("id", vec![/* subquery results */])
);

Aggregations

let mut query = SelectQuery::new("sales");
query.sum("amount", "total_sales")
     .count()
     .avg("order_value", "avg_order")
     .group_by_str("region")
     .having(where_like!("total_sales", "> 10000"));

Window Functions and Expressions

let mut query = SelectQuery::new("employees");
query.expr("ROW_NUMBER() OVER (ORDER BY salary DESC)", Some("rank"))
     .expr("CASE WHEN salary > 50000 THEN 'High' ELSE 'Low' END", Some("pay_grade"));

Error Handling

The library provides comprehensive error handling:

use kx_sql::error::Error;

match query.to_mysql() {
    Ok(sql) => println!("Generated SQL: {}", sql),
    Err(Error::NoUpdateFields) => {
        eprintln!("Update query needs at least one field");
    }
    Err(Error::NoDeleteConditions) => {
        eprintln!("DELETE queries require WHERE conditions for safety");
    }
    Err(e) => eprintln!("Other error: {}", e),
}

Performance

  • Zero-cost abstractions: All SQL generation happens at compile time
  • Memory efficient: Minimal allocations during query building
  • Type safety: Catch SQL errors at compile time, not runtime

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

This project is licensed under the same license as the parent workspace.

Dependencies

ID Version
anyhow ^1
chrono ^0.4
serde ^1
serde_json ^1.0.140
thiserror ^2
time ^0.3
tracing ^0.1
Details
Cargo
2026-01-14 17:48:33 +08:00
3
Caisin
Caisin
27 KiB
Assets (1)
Versions (1) View all
0.1.3 2026-01-14