1use toasty::schema::{
2 app::FieldTy,
3 mapping::{self, FieldEnum, FieldPrimitive},
4};
5
6use crate::{helpers::column, prelude::*};
7
8use toasty_core::{
9 driver::Operation,
10 stmt::{Assignment, BinaryOp, Expr, ExprSet, Statement, Value},
11};
12
13#[driver_test(id(ID))]
19pub async fn create_and_query_enum(t: &mut Test) -> Result<()> {
20 #[derive(Debug, PartialEq, toasty::Embed)]
21 enum Status {
22 #[column(variant = 1)]
23 Pending,
24 #[column(variant = 2)]
25 Active,
26 #[column(variant = 3)]
27 Done,
28 }
29
30 #[derive(Debug, toasty::Model)]
31 struct User {
32 #[key]
33 #[auto]
34 id: ID,
35 name: String,
36 status: Status,
37 }
38
39 let mut db = t.setup_db(models!(User, Status)).await;
40 let user_table = table_id(&db, "users");
41
42 t.log().clear();
44
45 let mut user = User::create()
46 .name("Alice")
47 .status(Status::Pending)
48 .exec(&mut db)
49 .await?;
50
51 let sql = t.capability().sql;
57 let status_pos = if driver_test_cfg!(id_u64) { 1 } else { 2 };
58 let status_pat = if sql {
59 ArgOr::Arg(status_pos)
60 } else {
61 ArgOr::Value(1i64)
62 };
63 let op = t.log().pop_op();
64 assert_struct!(op, Operation::QuerySql({
65 stmt: Statement::Insert({
66 source.body: ExprSet::Values({
67 rows: [=~ (Any, Any, status_pat)],
68 }),
69 target: toasty_core::stmt::InsertTarget::Table({
70 table: == user_table,
71 columns: == columns(&db, "users", &["id", "name", "status"]),
72 }),
73 }),
74 }));
75 if sql {
76 assert_struct!(op, Operation::QuerySql({
77 params[status_pos].value: == 1i64,
78 }));
79 }
80
81 let found = User::get_by_id(&mut db, &user.id).await?;
83 assert_eq!(found.status, Status::Pending);
84
85 t.log().clear();
87 user.update().status(Status::Active).exec(&mut db).await?;
88
89 if t.capability().sql {
92 assert_struct!(t.log().pop_op(), Operation::QuerySql({
93 stmt: Statement::Update({
94 target: toasty_core::stmt::UpdateTarget::Table(== user_table),
95 assignments: #{ [2]: Assignment::Set(Expr::Arg({ position: 0 }))},
96 }),
97 params: [{ value: == 2i64 }, ..],
98 }));
99 } else {
100 assert_struct!(t.log().pop_op(), Operation::UpdateByKey({
101 table: == user_table,
102 filter: None,
103 keys: _,
104 assignments: #{ [2]: Assignment::Set(== 2i64)},
105 returning: false,
106 }));
107 }
108
109 let found = User::get_by_id(&mut db, &user.id).await?;
110 assert_eq!(found.status, Status::Active);
111
112 User::filter_by_id(user.id)
114 .update()
115 .status(Status::Done)
116 .exec(&mut db)
117 .await?;
118
119 let found = User::get_by_id(&mut db, &user.id).await?;
120 assert_eq!(found.status, Status::Done);
121
122 let id = user.id;
124 user.delete().exec(&mut db).await?;
125 assert_err!(User::get_by_id(&mut db, &id).await);
126 Ok(())
127}
128
129#[driver_test(requires(scan))]
136pub async fn filter_by_enum_variant(t: &mut Test) -> Result<()> {
137 #[derive(Debug, PartialEq, toasty::Embed)]
138 enum Status {
139 #[column(variant = 1)]
140 Pending,
141 #[column(variant = 2)]
142 Active,
143 #[column(variant = 3)]
144 Done,
145 }
146
147 #[derive(Debug, toasty::Model)]
148 #[allow(dead_code)]
149 struct Task {
150 #[key]
151 #[auto]
152 id: uuid::Uuid,
153 name: String,
154 status: Status,
155 }
156
157 let mut db = t.setup_db(models!(Task, Status)).await;
158
159 for (name, status) in [
161 ("Task A", Status::Pending),
162 ("Task B", Status::Active),
163 ("Task C", Status::Active),
164 ("Task D", Status::Done),
165 ] {
166 Task::create()
167 .name(name)
168 .status(status)
169 .exec(&mut db)
170 .await?;
171 }
172
173 let status_col = column(&db, "tasks", "status");
174 t.log().clear();
175
176 let active = Task::filter(Task::fields().status().eq(Status::Active))
178 .exec(&mut db)
179 .await?;
180 assert_eq!(active.len(), 2);
181 {
182 let (op, _) = t.log().pop();
183 if t.capability().sql {
184 assert_struct!(op, Operation::QuerySql({
185 stmt: Statement::Query({
186 body: ExprSet::Select({
187 filter.expr: Some(Expr::BinaryOp({
188 lhs.as_expr_column_unwrap().column: == status_col.index,
189 op: BinaryOp::Eq,
190 *rhs: Expr::Arg({ position: 0 }),
191 })),
192 }),
193 }),
194 params: [{ value: == 2i64 }],
195 }));
196 } else {
197 assert_struct!(op, Operation::Scan({
198 filter: Some(Expr::BinaryOp({
199 lhs.as_expr_column_unwrap().column: == status_col.index,
200 op: BinaryOp::Eq,
201 *rhs: Expr::Value(== Value::I64(2)),
202 })),
203 }));
204 }
205 }
206
207 let pending = Task::filter(Task::fields().status().eq(Status::Pending))
209 .exec(&mut db)
210 .await?;
211 assert_eq!(pending.len(), 1);
212 assert_eq!(pending[0].name, "Task A");
213 {
214 let (op, _) = t.log().pop();
215 if t.capability().sql {
216 assert_struct!(op, Operation::QuerySql({
217 stmt: Statement::Query({
218 body: ExprSet::Select({
219 filter.expr: Some(Expr::BinaryOp({
220 lhs.as_expr_column_unwrap().column: == status_col.index,
221 op: BinaryOp::Eq,
222 *rhs: Expr::Arg({ position: 0 }),
223 })),
224 }),
225 }),
226 params: [{ value: == 1i64 }],
227 }));
228 } else {
229 assert_struct!(op, Operation::Scan({
230 filter: Some(Expr::BinaryOp({
231 lhs.as_expr_column_unwrap().column: == status_col.index,
232 op: BinaryOp::Eq,
233 *rhs: Expr::Value(== Value::I64(1)),
234 })),
235 }));
236 }
237 }
238
239 let done = Task::filter(Task::fields().status().eq(Status::Done))
241 .exec(&mut db)
242 .await?;
243 assert_eq!(done.len(), 1);
244 assert_eq!(done[0].name, "Task D");
245 {
246 let (op, _) = t.log().pop();
247 if t.capability().sql {
248 assert_struct!(op, Operation::QuerySql({
249 stmt: Statement::Query({
250 body: ExprSet::Select({
251 filter.expr: Some(Expr::BinaryOp({
252 lhs.as_expr_column_unwrap().column: == status_col.index,
253 op: BinaryOp::Eq,
254 *rhs: Expr::Arg({ position: 0 }),
255 })),
256 }),
257 }),
258 params: [{ value: == 3i64 }],
259 }));
260 } else {
261 assert_struct!(op, Operation::Scan({
262 filter: Some(Expr::BinaryOp({
263 lhs.as_expr_column_unwrap().column: == status_col.index,
264 op: BinaryOp::Eq,
265 *rhs: Expr::Value(== Value::I64(3)),
266 })),
267 }));
268 }
269 }
270
271 Ok(())
272}
273
274#[driver_test]
277pub async fn basic_embedded_enum(test: &mut Test) {
278 #[derive(toasty::Embed)]
279 enum Status {
280 #[column(variant = 1)]
281 Pending,
282 #[column(variant = 2)]
283 Active,
284 #[column(variant = 3)]
285 Done,
286 }
287
288 let db = test.setup_db(models!(Status)).await;
289 let schema = db.schema();
290
291 assert_struct!(schema.app.models, #{
293 Status::id(): toasty::schema::app::Model::EmbeddedEnum({
294 name.upper_camel_case(): "Status",
295 variants: [
296 _ { name.upper_camel_case(): "Pending", discriminant: toasty_core::stmt::Value::I64(1), .. },
297 _ { name.upper_camel_case(): "Active", discriminant: toasty_core::stmt::Value::I64(2), .. },
298 _ { name.upper_camel_case(): "Done", discriminant: toasty_core::stmt::Value::I64(3), .. },
299 ],
300 }),
301 });
302
303 assert!(schema.db.tables.is_empty());
305}
306
307#[driver_test]
312pub async fn root_model_with_embedded_enum_field(test: &mut Test) {
313 #[derive(toasty::Embed)]
314 enum Status {
315 #[column(variant = 1)]
316 Pending,
317 #[column(variant = 2)]
318 Active,
319 #[column(variant = 3)]
320 Done,
321 }
322
323 #[derive(toasty::Model)]
324 struct User {
325 #[key]
326 id: String,
327 #[allow(dead_code)]
328 status: Status,
329 }
330
331 let db = test.setup_db(models!(User, Status)).await;
332 let schema = db.schema();
333
334 assert_struct!(schema.app.models, #{
336 Status::id(): toasty::schema::app::Model::EmbeddedEnum({
337 name.upper_camel_case(): "Status",
338 variants.len(): 3,
339 }),
340 User::id(): toasty::schema::app::Model::Root({
341 name.upper_camel_case(): "User",
342 fields: [
343 { name.app: Some("id") },
344 {
345 name.app: Some("status"),
346 ty: FieldTy::Embedded({
347 target: == Status::id(),
348 }),
349 },
350 ],
351 }),
352 });
353
354 assert_struct!(schema.db.tables, [
356 {
357 name: =~ r"users$",
358 columns: [
359 { name: "id" },
360 { name: "status" },
361 ],
362 },
363 ]);
364
365 let user = &schema.app.models[&User::id()];
366 let user_table = schema.table_for(user);
367 let user_mapping = &schema.mapping.models[&User::id()];
368
369 assert_struct!(user_mapping, {
370 columns.len(): 2,
371 fields: [
372 mapping::Field::Primitive(FieldPrimitive {
373 column: == user_table.columns[0].id,
374 lowering: 0,
375 ..
376 }),
377 mapping::Field::Enum(FieldEnum {
378 discriminant: FieldPrimitive {
379 column: == user_table.columns[1].id,
380 lowering: 1,
381 ..
382 },
383 variants.len(): 3,
384 ..
385 }),
386 ],
387 });
388}