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
//! # raisfast-derive
//!
//! Proc-macro crate for the `raisfast` blog/CMS system.
//!
//! Provides two categories of macros:
//!
//! ## 1. Derive macros
//!
//! - **`#[derive(EventMeta)]`** — auto-generates `name()`, `display_name()`, `table()` methods
//! on event enums, with per-variant `#[event(table = "...", name = "...", dynamic)]` attributes.
//!
//! ## 2. Attribute macros
//!
//! - **`#[aspect_service(entity = "...", model = Type)]`** — generates a service struct with
//! before/after hooks that delegate to the aspect engine, and auto-emits domain events.
//!
//! ## 3. Bang macros (SQL CRUD helpers)
//!
//! All macros use optional `tenant: expr` named section for tenant filtering.
//! When provided, the SQL includes `AND tenant_id = ?` at runtime.
//!
//! | Macro | SQL operation |
//! |-------|---------------|
//! | `crud_delete!` | `DELETE FROM ... WHERE WhereExpr` |
//! | `crud_insert!` | `INSERT INTO ... (...) VALUES (...)` |
//! | `crud_scalar!` | `SELECT scalar ...` |
//! | `crud_query!` | `SELECT ...` via `query_as` |
//! | `crud_find!` | `SELECT cols FROM ... WHERE WhereExpr` → `fetch_optional` |
//! | `crud_find_one!` | same → `fetch_one` |
//! | `crud_find_all!` | same → `fetch_all` |
//! | `crud_list!` | `SELECT cols FROM ...` → `fetch_all` (no WHERE) |
//! | `crud_update!` | `UPDATE ... SET ... WHERE WhereExpr` |
//! | `crud_count!` | `SELECT COUNT(*) FROM ... WHERE WhereExpr` |
//! | `crud_exists!` | `SELECT EXISTS(SELECT 1 ... WHERE WhereExpr)` |
//! | `crud_query_paged!` | paginated data + COUNT |
//! | `crud_join_paged!` | paginated JOIN + COUNT |
//! | `crud_resolve_id!` | `SELECT id FROM table WHERE id = ?` → `Option<i64>` |
//! | `crud_resolve_ids!` | `SELECT id FROM table WHERE id IN (...)` → `Vec<i64>` (validates all exist) |
//!
//! ### Schema validation
//!
//! - **`check_schema!("table", "col1", "col2", ...)`** — compile-time validation only;
//! expands to nothing. Emits a compile error if the table or any column is missing.
//!
//! ## Architecture notes
//!
//! - `schema.rs` — parses `schema.sqlite.sql` + `tenantable.sqlite.sql` at compile time
//! into a `Schema` struct. Used for table/column validation and for generating explicit
//! column lists (replacing `SELECT *`).
//! - `crud.rs` — all CRUD macro implementations + input parsing structs.
//! - `event_meta.rs` — `#[derive(EventMeta)]`.
//! - `aspect_service.rs` — `#[aspect_service]`.
use TokenStream;
/// Derive macro for event enums.
///
/// Generates `name()`, `display_name()`, `table()` methods.
/// Supports per-variant attributes: `#[event(table = "...", name = "...", dynamic)]`.
/// `crud_delete!(pool, "table", where: WhereExpr [, tenant: expr])`
///
/// Generates `DELETE FROM table WHERE ...` via `sqlx::query()`.
/// Uses Where DSL for conditions. When `tenant:` is provided, adds `AND tenant_id = ?` filter.
/// `crud_insert!(pool, "table", ["col1" => val1, "col2" => val2] [, tenant: expr])`
///
/// Generates `INSERT INTO table (cols) VALUES (placeholders)` via `sqlx::query!()`.
/// Values are pre-bound to `let` variables to avoid E0716 temporary lifetime issues.
/// `crud_scalar!(pool, Type, sql, [vals], method [, tenant: expr])`
///
/// Generates `sqlx::query_scalar::<_, Type>(sql)` with binds. Runtime query.
/// `crud_select!(pool, "table", ["col1", "col2"], where: WhereExpr [, tenant: expr])`
///
/// Generates `SELECT col1, col2 FROM table WHERE ...` via `sqlx::query_as`.
/// `crud_join!(pool, Type, select: [...], from: "...", joins: [...], where: WhereExpr [, tenant: expr, method: fetch_all, order_by: "...", limit: expr, offset: expr])`
///
/// Generates a JOIN query with optional Where DSL conditions and tenant filtering.
/// `crud_join_paged!(pool, Type, select: [...], from: "...", joins: [...], tenant_alias: "...", tenant: tid, order_by: "...", page: page, page_size: page_size)`
///
/// Generates a paginated JOIN query with COUNT. Returns `(Vec<T>, i64)`.
/// `crud_count!(pool, "table", where: WhereExpr [, tenant: expr])`
///
/// `SELECT COUNT(*) FROM table WHERE ...` → `i64`.
/// `crud_query!(pool, Type, sql, [vals], method [, tenant: expr])`
///
/// Generates `sqlx::query_as::<_, Type>(sql)` with binds. Runtime query.
/// `crud_find!(pool, "table", Type, where: WhereExpr [, tenant: expr, order_by: "expr"])`
///
/// `SELECT {all_columns} FROM table WHERE ...` → `fetch_optional`.
/// Column list is generated from schema (replaces `SELECT *`).
/// `crud_find_one!(...)` — same as `crud_find!` but uses `fetch_one`.
/// `crud_find_all!(...)` — same as `crud_find!` but uses `fetch_all`.
/// `crud_list!(pool, "table", Type)`
///
/// `SELECT {all_columns} FROM table` → `fetch_all`. No WHERE clause.
/// Optional: `order_by: "expr"` — appends `ORDER BY expr`.
/// Optional: `tenant: tid` — adds `WHERE 1=1 AND tenant_id = ?` filter.
///
/// ```ignore
/// crud_list!(pool, "tags" => Tag)
/// crud_list!(pool, "tags" => Tag, order_by: "name")
/// crud_list!(pool, "tags" => Tag, order_by: "name", tenant: tenant_id)
/// ```
/// `check_schema!("table", "col1", "col2", ...)`
///
/// Compile-time validation only — expands to nothing (empty token stream).
/// Emits a compile error if the table or any named column is missing from the schema.
/// `crud_exists!(pool, "table", where: WhereExpr [, tenant: expr])`
///
/// `SELECT EXISTS(SELECT 1 FROM table WHERE ...)` → `bool`.
/// Uses `sqlx::query_scalar` with compile-time verified SQL.
/// `crud_upsert!(pool, "table", key: ["conflict_col"], bind: ["col" => val, ...], update: ["col1", "col2"] [, tenant: expr])`
///
/// Generates `INSERT INTO table (...) VALUES (...) ON CONFLICT(...) DO UPDATE SET ...`
/// via `sqlx::query!()` (compile-time verified).
/// `crud_update!(pool, "table", bind: [...], optional: [...], raw: [...], where: WhereExpr [, tenant: tid])`
///
/// Generates a runtime `sqlx::query()` UPDATE. Supports `bind:` (always-set),
/// `optional:` (set only when Some), `raw:` (SQL expressions).
/// `crud_query_paged!(pool, Type, data_sql: "...", count_sql: "...", binds: [...], where: ["col" => opt_val, ...], tenant: tid, page: page, page_size: page_size)`
///
/// Generates a paginated query pair (data + COUNT) with optional tenant filtering
/// and optional dynamic WHERE conditions.
///
/// Both `data_sql` and `count_sql` are string literals. Use `{tenant}` as a placeholder
/// where the `AND tenant_id = ?` clause should be inserted when tenant_id is Some.
///
/// The `where:` section accepts optional values — `Some(val)` appends `AND col = ?`
/// and binds, `None` skips. Values must be `Option` types.
///
/// Returns `(Vec<T>, i64)` — the data rows and total count.
/// `crud_resolve_id!(pool, table, id [, tenant: expr])`
///
/// Resolves a SnowflakeId to an `i64` by verifying it exists in the target table.
/// Returns `sqlx::Result<Option<i64>>` — `Some(id)` if found, `None` if not.
///
/// The `table` parameter is a runtime `&str` expression (not a literal).
/// Performs `is_safe_identifier()` check before querying.
/// When `tenant:` is provided, adds `AND tenant_id = ?` filter.
/// `crud_resolve_ids!(pool, table, ids)`
///
/// Batch version of `crud_resolve_id!`. Resolves a slice of `i64` IDs by verifying
/// they ALL exist in the target table via a single `SELECT id FROM table WHERE id IN (...)`.
///
/// Returns `Result<Vec<i64>, sqlx::Error>` — the validated ID list on success.
/// Returns `sqlx::Error::RowNotFound` if any ID is missing.
/// Returns error on unsafe table name.
/// `#[aspect_service(entity = "posts", model = Post)]`
///
/// Attribute macro applied to a service struct. Generates:
/// - `new(...)` constructor
/// - `before_create(...)` / `before_update(...)` / `before_delete(...)` — delegate to aspect engine
/// - `after_created(...)` / `after_updated(...)` / `after_deleted(...)` — emit domain events
///
/// The struct must have one field marked with `#[engine]` pointing to the aspect engine.