Skip to content

Commit 27f3945

Browse files
authored
orm: support OR in dynamic where blocks (#27336)
1 parent 9aeb6bf commit 27f3945

6 files changed

Lines changed: 500 additions & 101 deletions

File tree

vlib/orm/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,33 @@ result := sql db {
245245
}!
246246
```
247247

248+
Dynamic ORM blocks can build `WHERE` and `SET` data conditionally. Commas between
249+
emitted dynamic `where` items are joined with `AND`; use `&&` and `||` inside an
250+
item for explicit boolean conditions.
251+
252+
```v ignore
253+
where_filter := {
254+
if name := req.name {
255+
name == name
256+
},
257+
id == user_id || tenant_id == tenant_id
258+
}
259+
260+
rows := sql db {
261+
dynamic select from Foo where where_filter
262+
}!
263+
264+
update_data := {
265+
name == new_name
266+
}
267+
268+
sql db {
269+
dynamic update Foo set update_data where {
270+
id == user_id || tenant_id == tenant_id
271+
}
272+
}!
273+
```
274+
248275
ORM select expressions also support built-in aggregate functions. `count` keeps
249276
its legacy syntax, while the other aggregates use SQL-like function calls.
250277

vlib/orm/orm_dynamic_test.v

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ mut:
2323
is_required u8
2424
}
2525

26+
@[table: 'dynamic_or_members']
27+
struct DynamicOrMember {
28+
mut:
29+
id int @[primary]
30+
tenant_id int
31+
name string
32+
status string
33+
}
34+
2635
fn test_dynamic_select_with_inline_where_block() {
2736
mut db := sqlite.connect(':memory:')!
2837
defer {
@@ -315,3 +324,182 @@ fn test_dynamic_select_with_explicit_order_by_asc() {
315324
assert rows[1].name == 'Alice'
316325
assert rows[1].age == 31
317326
}
327+
328+
fn test_dynamic_select_where_block_with_or_expression() {
329+
mut db := sqlite.connect(':memory:')!
330+
defer {
331+
db.close() or { panic(err) }
332+
}
333+
334+
sql db {
335+
create table DynamicOrMember
336+
}!
337+
338+
members := [
339+
DynamicOrMember{
340+
id: 1
341+
tenant_id: 1
342+
name: 'Alice'
343+
status: 'active'
344+
},
345+
DynamicOrMember{
346+
id: 2
347+
tenant_id: 2
348+
name: 'Bob'
349+
status: 'active'
350+
},
351+
DynamicOrMember{
352+
id: 3
353+
tenant_id: 2
354+
name: 'Charlie'
355+
status: 'pending'
356+
},
357+
DynamicOrMember{
358+
id: 4
359+
tenant_id: 3
360+
name: 'Diana'
361+
status: 'active'
362+
},
363+
]
364+
365+
for member in members {
366+
sql db {
367+
insert member into DynamicOrMember
368+
}!
369+
}
370+
371+
active := 'active'
372+
tenant_id := 2
373+
rows := sql db {
374+
dynamic select from DynamicOrMember where {
375+
status == active && (name == 'Alice' || tenant_id == tenant_id)
376+
} order by id
377+
}!
378+
379+
assert rows.map(it.name) == ['Alice', 'Bob']
380+
381+
grouped_rows := sql db {
382+
dynamic select from DynamicOrMember where {
383+
(name == 'Alice' || status == active) && tenant_id == tenant_id
384+
} order by id
385+
}!
386+
387+
assert grouped_rows.map(it.name) == ['Bob']
388+
}
389+
390+
fn test_dynamic_select_where_block_with_or_expression_and_comma_filter() {
391+
mut db := sqlite.connect(':memory:')!
392+
defer {
393+
db.close() or { panic(err) }
394+
}
395+
396+
sql db {
397+
create table DynamicOrMember
398+
}!
399+
400+
members := [
401+
DynamicOrMember{
402+
id: 1
403+
tenant_id: 1
404+
name: 'Alice'
405+
status: 'active'
406+
},
407+
DynamicOrMember{
408+
id: 2
409+
tenant_id: 2
410+
name: 'Bob'
411+
status: 'active'
412+
},
413+
DynamicOrMember{
414+
id: 3
415+
tenant_id: 2
416+
name: 'Charlie'
417+
status: 'pending'
418+
},
419+
DynamicOrMember{
420+
id: 4
421+
tenant_id: 3
422+
name: 'Diana'
423+
status: 'active'
424+
},
425+
]
426+
427+
for member in members {
428+
sql db {
429+
insert member into DynamicOrMember
430+
}!
431+
}
432+
433+
tenant_id := 2
434+
rows := sql db {
435+
dynamic select from DynamicOrMember where {
436+
tenant_id == tenant_id,
437+
name == 'Alice' || status == 'active'
438+
} order by id
439+
}!
440+
441+
assert rows.map(it.name) == ['Bob']
442+
}
443+
444+
fn test_dynamic_update_where_block_with_or_expression() {
445+
mut db := sqlite.connect(':memory:')!
446+
defer {
447+
db.close() or { panic(err) }
448+
}
449+
450+
sql db {
451+
create table DynamicOrMember
452+
}!
453+
454+
members := [
455+
DynamicOrMember{
456+
id: 1
457+
tenant_id: 1
458+
name: 'Alice'
459+
status: 'active'
460+
},
461+
DynamicOrMember{
462+
id: 2
463+
tenant_id: 2
464+
name: 'Bob'
465+
status: 'active'
466+
},
467+
DynamicOrMember{
468+
id: 3
469+
tenant_id: 2
470+
name: 'Charlie'
471+
status: 'pending'
472+
},
473+
DynamicOrMember{
474+
id: 4
475+
tenant_id: 3
476+
name: 'Diana'
477+
status: 'active'
478+
},
479+
]
480+
481+
for member in members {
482+
sql db {
483+
insert member into DynamicOrMember
484+
}!
485+
}
486+
487+
next_status := 'archived'
488+
update_expr := {
489+
status == next_status
490+
}
491+
user_id := 1
492+
tenant_id := 2
493+
494+
sql db {
495+
dynamic update DynamicOrMember set update_expr where {
496+
id == user_id || tenant_id == tenant_id
497+
}
498+
}!
499+
500+
rows := sql db {
501+
select from DynamicOrMember order by id
502+
}!
503+
504+
assert rows.map(it.status) == ['archived', 'archived', 'archived', 'active']
505+
}

0 commit comments

Comments
 (0)