squeal 0.1.0

A SQL query builder library for Rust
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# squeal

A type-safe SQL query builder for Rust targeting PostgreSQL.

[![License: LGPL v3](https://img.shields.io/badge/License-LGPL%20v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)

**squeal** provides a simple, type-safe way to construct SQL queries using Rust structures. It offers both direct struct construction and fluent builder APIs with escape hatches built in for complex use cases.

## Philosophy

- Keep it simple & stupid
- No attributes, macros, or other "magic"
- Escape hatches built in for custom SQL
- Any valid construction produces syntactically valid SQL

## Features

- **Type-safe query construction** - Catch errors at compile time
- **Fluent builder API** - Chain methods for readable query construction
- **Direct struct construction** - Full control when needed
- **PostgreSQL targeting** - Designed for PostgreSQL dialect
- **Zero ORM runtime overhead** - Queries are built, not interpreted.

### Supported Operations

- `SELECT` queries with WHERE, GROUP BY, HAVING, ORDER BY, LIMIT, OFFSET
- `JOIN` operations (INNER, LEFT, RIGHT, FULL, CROSS) with table or subquery sources
- `WITH` / Common Table Expressions (CTEs) for complex queries
- `INSERT` statements with single or multiple value sets, or from SELECT queries
- `INSERT ... ON CONFLICT` (UPSERT) with DO NOTHING or DO UPDATE
- `UPDATE` statements with SET and WHERE clauses
- `DELETE` statements with WHERE conditions
- `RETURNING` clauses for INSERT, UPDATE, and DELETE
- `CREATE TABLE` DDL statements
- `DROP TABLE` DDL statements

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
squeal = "0.1.0"
```

**MSRV**: Rust 1.90.0

## Quick Start

```rust
use squeal::*;

// SELECT query with fluent builder
let query = Q()
    .select(vec!["id", "name", "email"])
    .from("users")
    .where_(eq("active", "true"))
    .order_by(vec![OrderedColumn::Desc("created_at")])
    .limit(10)
    .build();

assert_eq!(
    query.sql(),
    "SELECT id, name, email FROM users WHERE active = true ORDER BY created_at DESC LIMIT 10"
);

// INSERT with fluent builder
let insert = I("users")
    .columns(vec!["name", "email"])
    .values(vec!["'Alice'", "'alice@example.com'"])
    .build();

assert_eq!(
    insert.sql(),
    "INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')"
);

// UPDATE with fluent builder
let update = U("users")
    .columns(vec!["status"])
    .values(vec!["'inactive'"])
    .where_(lt("last_login", "'2024-01-01'"))
    .build();

assert_eq!(
    update.sql(),
    "UPDATE users SET status = 'inactive' WHERE last_login < '2024-01-01'"
);

// DELETE with fluent builder
let delete = D("logs")
    .where_(lt("created_at", "NOW() - INTERVAL '30 days'"))
    .build();
```

### Direct Struct Construction

For maximum control, you can construct query structs directly:

```rust
use squeal::*;

let query = Query {
    with_clause: None,
    select: Some(Select::new(Columns::Star, None)),
    from: Some(FromSource::Table("products")),
    joins: vec![],
    where_clause: None,
    group_by: None,
    having: None,
    order_by: Some(vec![OrderedColumn::Asc("price")]),
    limit: Some(100),
    offset: Some(0),
    for_update: false,
};

assert_eq!(query.sql(), "SELECT * FROM products ORDER BY price ASC LIMIT 100 OFFSET 0");
```

### Escape Hatches

Use custom operators and raw SQL fragments when needed:

```rust
use squeal::*;

// Use Op::O for custom PostgreSQL operators
let custom_op = Term::Condition(
    Box::new(Term::Atom("data")),
    Op::O("@>"),  // PostgreSQL JSONB contains operator
    Box::new(Term::Atom("'{\"type\": \"click\"}'"))
);

let query = Q()
    .select(vec!["*"])
    .from("events")
    .where_(custom_op)
    .build();
```

## Advanced Features

### JOINs

Combine data from multiple tables with type-safe JOIN operations:

```rust
use squeal::*;

// Find all users with their order count
let query = Q()
    .select(vec!["users.name", "users.email", "COUNT(orders.id) as order_count"])
    .from("users")
    .inner_join("orders", eq("users.id", "orders.user_id"))
    .group_by(vec!["users.id", "users.name", "users.email"])
    .order_by(vec![OrderedColumn::Desc("order_count")])
    .build();

// LEFT JOIN to include users with no orders
let query_with_nulls = Q()
    .select(vec!["users.name", "COALESCE(orders.total, 0) as total"])
    .from("users")
    .left_join("orders", eq("users.id", "orders.user_id"))
    .build();
```

### Common Table Expressions (WITH clause)

Build complex queries with CTEs for better readability:

```rust
use squeal::*;

// Calculate monthly revenue and compare to average
let monthly_revenue = Q()
    .select(vec![
        "DATE_TRUNC('month', created_at) as month",
        "SUM(total) as revenue"
    ])
    .from("orders")
    .group_by(vec!["month"])
    .build();

let query = Q()
    .with("monthly_revenue", monthly_revenue)
    .select(vec![
        "month",
        "revenue",
        "(revenue - AVG(revenue) OVER ()) as diff_from_avg"
    ])
    .from("monthly_revenue")
    .order_by(vec![OrderedColumn::Desc("month")])
    .build();

// Result: WITH monthly_revenue AS (SELECT ...) SELECT month, revenue, ...
```

### UPSERT (INSERT ... ON CONFLICT)

Handle unique constraint violations gracefully:

```rust
use squeal::*;

// Insert user, do nothing if email already exists
let insert = I("users")
    .columns(vec!["email", "name", "created_at"])
    .values(vec!["'alice@example.com'", "'Alice'", "NOW()"])
    .on_conflict_do_nothing(vec!["email"])
    .build();

// Result: INSERT INTO users (email, name, created_at) VALUES (...)
//         ON CONFLICT (email) DO NOTHING

// Insert or update: update the name if email exists
let upsert = I("users")
    .columns(vec!["email", "name", "login_count"])
    .values(vec!["'bob@example.com'", "'Bob Smith'", "'1'"])
    .on_conflict_do_update(
        vec!["email"],
        vec![
            ("name", "'Bob Smith'"),
            ("login_count", "users.login_count + 1"),
            ("updated_at", "NOW()")
        ]
    )
    .returning(Columns::Selected(vec!["id", "email", "updated_at"]))
    .build();

// Result: INSERT INTO users (...) VALUES (...)
//         ON CONFLICT (email) DO UPDATE SET name = '...', login_count = ...
//         RETURNING id, email, updated_at
```

### Multiple Row INSERT

Efficiently insert multiple rows in a single statement:

```rust
use squeal::*;

let insert = I("products")
    .columns(vec!["name", "price", "category"])
    .rows(vec![
        vec!["'Laptop'", "'999.99'", "'electronics'"],
        vec!["'Mouse'", "'24.99'", "'electronics'"],
        vec!["'Desk'", "'299.99'", "'furniture'"],
    ])
    .returning(Columns::Selected(vec!["id", "name"]))
    .build();

// Result: INSERT INTO products (name, price, category) VALUES
//         ('Laptop', 999.99, 'electronics'),
//         ('Mouse', 24.99, 'electronics'),
//         ('Desk', 299.99, 'furniture')
//         RETURNING id, name
```

### INSERT ... SELECT

Copy data from one table to another:

```rust
use squeal::*;

// Archive old orders
let select_old_orders = Q()
    .select(vec!["id", "user_id", "total", "created_at"])
    .from("orders")
    .where_(lt("created_at", "'2023-01-01'"))
    .build();

let archive = I("orders_archive")
    .columns(vec!["order_id", "user_id", "total", "order_date"])
    .select(select_old_orders)
    .build();

// Result: INSERT INTO orders_archive (order_id, user_id, total, order_date)
//         SELECT id, user_id, total, created_at FROM orders
//         WHERE created_at < '2023-01-01'
```

### RETURNING Clause

Get values from INSERT, UPDATE, or DELETE operations:

```rust
use squeal::*;

// Get the auto-generated ID after insert
let insert = I("posts")
    .columns(vec!["title", "content", "author_id"])
    .values(vec!["'Hello World'", "'First post!'", "'1'"])
    .returning(Columns::Selected(vec!["id", "created_at"]))
    .build();

// Update and return the modified rows
let update = U("users")
    .columns(vec!["status", "updated_at"])
    .values(vec!["'inactive'", "NOW()"])
    .where_(lt("last_login", "NOW() - INTERVAL '1 year'"))
    .returning(Columns::Selected(vec!["id", "email"]))
    .build();

// Delete and track what was removed
let delete = D("sessions")
    .where_(lt("expires_at", "NOW()"))
    .returning(Columns::Selected(vec!["user_id", "session_id"]))
    .build();
```

## Development

### Build and Test

```bash
# Build the project
cargo build

# Run tests (excludes Docker tests)
cargo test

# Run all tests including PostgreSQL integration tests (requires Docker)
cargo test --features postgres-docker

# Run benchmarks
cargo bench

# Generate documentation
cargo doc --open
```

### Code Quality

```bash
# Run clippy (required before committing)
cargo clippy

# Auto-fix clippy warnings
cargo clippy --fix --lib -p squeal
```

## Project Status

**Version**: 0.1.0 (pre-release)

This library is in active development. The API is stabilizing but may
still change. It is suitable for experimentation and early adoption,
but not recommended for production use until version 1.0.

## Design Notes

A core problem of using SQL in programming is the impedence mismatch
between the code of the database and the code of the client.

Efforts such as `ActiveRecord` exist; they are widely panned by SQL
experts as producing poor SQL and tend not to "fit" the database
itself. Writing SQL directly in code has also a poor history - it is
notorious for defects and difficulty in maintaining.

_Query builders_ occupy a middle point, where the power of the
programming language brings to bear some maintainability, but the
intermediate artifact is still recognizable and controllable by the
programmer, leading to a fit with the database.

This is my contribution in this effort; I developed a Scala query
builder for a company quite a few years ago now and it worked well. I
am pleased to offer a ground-up cleanroom Rust query builder.

The core problem this design does not touch is the object-relation
mapping code. I would advise users to write a database model layer,
perform the object-relation map there, then transform into the usual
in-system state from that point out.

## Related Projects

- PostgreSQL Rust driver: [rust-postgres]https://docs.rs/postgres/latest/postgres/
- Similar library for Go: [sqlf]https://github.com/leporo/sqlf

## Repository

- **GitHub**: https://github.com/upside-down-research/squeal

## License

LGPL 3.0 or later. See [LICENSE.md](LICENSE.md) for details.

## Contributing

Contributions are welcome! Please ensure:
- All tests pass (`cargo test`)
- No clippy warnings (`cargo clippy`)
- New features include tests
- No decrease in code coverage (tarpaulin).
- Code follows the existing style
- Idealy no dependencies are taken outside of dev-dependencies.
- No unsafe code.


### AI Note.

This library was hand written, and later AI was asked to extend it. A
human contributing _takes responsibility_for their code, whether or
not an AI was involved.