sqlx-record/.claude/skills/sqlx-values.md

5.6 KiB

sqlx-record Values Skill

Guide to Value types and database binding.

Triggers

  • "value type", "sql value"
  • "bind value", "query parameter"
  • "type conversion"

Value Enum

pub enum Value {
    // Integers
    Int8(i8),
    Uint8(u8),
    Int16(i16),
    Uint16(u16),
    Int32(i32),
    Uint32(u32),
    Int64(i64),
    Uint64(u64),

    // Other primitives
    String(String),
    Bool(bool),
    VecU8(Vec<u8>),

    // Special types
    Uuid(uuid::Uuid),
    NaiveDate(NaiveDate),
    NaiveDateTime(NaiveDateTime),
}

Auto-Conversions (From trait)

// Strings
Value::from("hello")              // String
Value::from("hello".to_string())  // String

// Integers
Value::from(42i32)                // Int32
Value::from(42i64)                // Int64
Value::from(&42i32)               // Int32 (from reference)
Value::from(&42i64)               // Int64 (from reference)

// Boolean
Value::from(true)                 // Bool
Value::from(&true)                // Bool (from reference)

// UUID
Value::from(uuid::Uuid::new_v4()) // Uuid
Value::from(&some_uuid)           // Uuid (from reference)

// Dates
Value::from(NaiveDate::from_ymd(2024, 1, 15))  // NaiveDate
Value::from(NaiveDateTime::new(...))           // NaiveDateTime

values! Macro

// Empty
values![]

// Single value (auto-converts)
values!["hello"]
values![42]
values![true]

// Multiple values
values!["name", 30, true, uuid]

// With explicit types
values![
    Value::String("test".into()),
    Value::Int32(42),
    Value::Bool(true)
]

Database-Specific Handling

Unsigned Integers

Type MySQL PostgreSQL SQLite
Uint8 Native u8 Cast to i16 Cast to i16
Uint16 Native u16 Cast to i32 Cast to i32
Uint32 Native u32 Cast to i64 Cast to i64
Uint64 Native u64 Cast to i64* Cast to i64*

*Note: Uint64 values > i64::MAX will overflow when cast.

UUID Storage

Database Type Notes
MySQL BINARY(16) Stored as bytes
PostgreSQL UUID Native type
SQLite BLOB Stored as bytes

JSON Storage

Database Type
MySQL JSON
PostgreSQL JSONB
SQLite TEXT

Bind Functions

bind_values

Bind values to a raw Query:

use sqlx_record::prelude::*;

let query = sqlx::query("SELECT * FROM users WHERE name = ? AND age > ?");
let query = bind_values(query, &values!["Alice", 18]);
let rows = query.fetch_all(&pool).await?;

bind_as_values

Bind values to a typed QueryAs:

let query = sqlx::query_as::<_, User>("SELECT * FROM users WHERE id = ?");
let query = bind_as_values(query, &values![user_id]);
let user = query.fetch_optional(&pool).await?;

bind_scalar_values

Bind values to a scalar QueryScalar:

let query = sqlx::query_scalar("SELECT COUNT(*) FROM users WHERE active = ?");
let query = bind_scalar_values(query, &values![true]);
let count: i64 = query.fetch_one(&pool).await?;

BindValues Trait

Extension trait for fluent binding:

use sqlx_record::prelude::BindValues;

let users = sqlx::query_as::<_, User>("SELECT * FROM users WHERE role = ?")
    .bind_values(&values!["admin"])
    .fetch_all(&pool)
    .await?;

Placeholder Function

Database-specific placeholder generation:

use sqlx_record::prelude::placeholder;

let ph1 = placeholder(1);
let ph2 = placeholder(2);

// MySQL/SQLite: "?", "?"
// PostgreSQL:   "$1", "$2"

let sql = format!("SELECT * FROM users WHERE id = {} AND role = {}", ph1, ph2);

With Filters

Filters automatically use Value internally:

// These are equivalent:
filters![("name", "Alice")]
filters![("name", Value::String("Alice".into()))]

// Values are extracted for binding:
let (where_clause, values) = Filter::build_where_clause(&filters);
// values: Vec<Value>

Custom Queries with Values

use sqlx_record::prelude::*;

async fn complex_query(pool: &Pool, status: &str, min_age: i32) -> Result<Vec<User>> {
    let values = values![status, min_age];

    let sql = format!(
        "SELECT * FROM users WHERE status = {} AND age >= {} ORDER BY name",
        placeholder(1),
        placeholder(2)
    );

    let query = sqlx::query_as::<_, User>(&sql);
    let query = bind_as_values(query, &values);

    query.fetch_all(pool).await
}

Updater Enum

For more complex update patterns:

pub enum Updater<'a> {
    Set(&'a str, Value),        // SET field = value
    Increment(&'a str, Value),  // SET field = field + value
    Decrement(&'a str, Value),  // SET field = field - value
}

Type Helpers

query_fields

Extract field names from aliased field list:

let fields = vec!["id", "name as user_name", "email"];
let result = query_fields(fields);
// "id, name, email"

Common Patterns

Dynamic WHERE Clause

fn build_query(filters: &[(&str, Value)]) -> (String, Vec<Value>) {
    let mut conditions = Vec::new();
    let mut values = Vec::new();

    for (i, (field, value)) in filters.iter().enumerate() {
        conditions.push(format!("{} = {}", field, placeholder(i + 1)));
        values.push(value.clone());
    }

    let where_clause = if conditions.is_empty() {
        String::new()
    } else {
        format!("WHERE {}", conditions.join(" AND "))
    };

    (where_clause, values)
}

Batch Insert Values

fn batch_values(users: &[User]) -> Vec<Value> {
    users.iter().flat_map(|u| {
        vec![
            Value::Uuid(u.id),
            Value::String(u.name.clone()),
            Value::String(u.email.clone()),
        ]
    }).collect()
}