Skip to content

Commit 8f8a491

Browse files
authored
orm: supoprt DISTINCT keyword (#26163)
1 parent ba97785 commit 8f8a491

8 files changed

Lines changed: 98 additions & 18 deletions

File tree

‎vlib/orm/orm.v‎

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -184,17 +184,18 @@ pub mut:
184184
// types - Types to select
185185
pub struct SelectConfig {
186186
pub mut:
187-
table Table
188-
is_count bool
189-
has_where bool
190-
has_order bool
191-
order string
192-
order_type OrderType
193-
has_limit bool
194-
primary string = 'id' // should be set if primary is different than 'id' and 'has_limit' is false
195-
has_offset bool
196-
fields []string
197-
types []int
187+
table Table
188+
is_count bool
189+
has_where bool
190+
has_order bool
191+
order string
192+
order_type OrderType
193+
has_limit bool
194+
primary string = 'id' // should be set if primary is different than 'id' and 'has_limit' is false
195+
has_offset bool
196+
has_distinct bool
197+
fields []string
198+
types []int
198199
}
199200

200201
// Interfaces gets called from the backend and can be implemented
@@ -349,6 +350,10 @@ pub fn orm_stmt_gen(sql_dialect SQLDialect, table Table, q string, kind StmtKind
349350
pub fn orm_select_gen(cfg SelectConfig, q string, num bool, qm string, start_pos int, where QueryData) string {
350351
mut str := 'SELECT '
351352

353+
if cfg.has_distinct {
354+
str += 'DISTINCT '
355+
}
356+
352357
if cfg.is_count {
353358
str += 'COUNT(*)'
354359
} else {

‎vlib/orm/orm_fn_test.v‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,35 @@ fn test_orm_select_gen_with_all() {
168168
assert query == "SELECT 'id', 'test', 'abc' FROM 'test_table' WHERE 'abc' = ?0 AND 'test' > ?1 ORDER BY '' DESC LIMIT ?2 OFFSET ?3;"
169169
}
170170

171+
fn test_orm_select_gen_with_distinct() {
172+
query := orm.orm_select_gen(orm.SelectConfig{
173+
table: orm.Table{
174+
name: 'test_table'
175+
}
176+
fields: get_select_fields()
177+
has_distinct: true
178+
}, "'", true, '?', 0, orm.QueryData{})
179+
180+
assert query == "SELECT DISTINCT 'id', 'test', 'abc' FROM 'test_table';"
181+
}
182+
183+
fn test_orm_select_gen_with_distinct_and_where() {
184+
query := orm.orm_select_gen(orm.SelectConfig{
185+
table: orm.Table{
186+
name: 'test_table'
187+
}
188+
fields: get_select_fields()
189+
has_distinct: true
190+
has_where: true
191+
}, "'", true, '?', 0, orm.QueryData{
192+
fields: ['abc']
193+
kinds: [.eq]
194+
is_and: []
195+
})
196+
197+
assert query == "SELECT DISTINCT 'id', 'test', 'abc' FROM 'test_table' WHERE 'abc' = ?0;"
198+
}
199+
171200
fn test_orm_table_gen() {
172201
table := orm.Table{
173202
name: 'test_table'

‎vlib/orm/orm_null_test.v‎

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,21 @@ fn test_inserting_passed_optionals() {
314314
assert res3[1].name == none
315315
assert res3[2].name or { '' } == 'www'
316316
}
317+
318+
fn test_distinct_select() {
319+
db := MockDB.new()
320+
321+
sql db {
322+
create table Foo
323+
}!
324+
325+
_ := sql db {
326+
select distinct from Foo
327+
}!
328+
assert db.st.last == 'SELECT DISTINCT `id`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` FROM `foo`;'
329+
330+
_ := sql db {
331+
select distinct from Foo where e > 5
332+
}!
333+
assert db.st.last == 'SELECT DISTINCT `id`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` FROM `foo` WHERE `e` > ?;'
334+
}

‎vlib/orm/orm_test.v‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,3 +409,17 @@ fn test_orm() {
409409
drop table TestTime
410410
}!
411411
}
412+
413+
fn test_distinct() {
414+
db := sqlite.connect(':memory:') or { panic(err) }
415+
416+
// Create table without unique constraints to allow true duplicates
417+
db.exec('CREATE TABLE items (name TEXT, category TEXT)')!
418+
db.exec("INSERT INTO items VALUES ('Apple', 'Fruit'), ('Apple', 'Fruit'), ('Banana', 'Fruit')")!
419+
420+
without_distinct := db.exec('SELECT name, category FROM items')!
421+
assert without_distinct.len == 3
422+
423+
with_distinct := db.exec('SELECT DISTINCT name, category FROM items')!
424+
assert with_distinct.len == 2
425+
}

‎vlib/v/ast/ast.v‎

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2292,12 +2292,13 @@ pub:
22922292
is_insert bool // for insert expressions
22932293
inserted_var string
22942294

2295-
has_where bool
2296-
has_order bool
2297-
has_limit bool
2298-
has_offset bool
2299-
has_desc bool
2300-
is_array bool
2295+
has_where bool
2296+
has_order bool
2297+
has_limit bool
2298+
has_offset bool
2299+
has_desc bool
2300+
has_distinct bool
2301+
is_array bool
23012302
// is_generated indicates a statement is generated by ORM for complex queries with related tables.
23022303
is_generated bool
23032304
pos token.Pos

‎vlib/v/fmt/fmt.v‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3083,6 +3083,9 @@ pub fn (mut f Fmt) sql_expr(node ast.SqlExpr) {
30833083
} else {
30843084
f.write('\tselect ')
30853085
}
3086+
if node.has_distinct {
3087+
f.write('distinct ')
3088+
}
30863089
sym := f.table.sym(node.table_expr.typ)
30873090
mut table_name := sym.name
30883091
if !table_name.starts_with('C.') && !table_name.starts_with('JS.') {

‎vlib/v/gen/c/orm.v‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -977,6 +977,7 @@ fn (mut g Gen) write_orm_select(node ast.SqlExpr, connection_var_name string, re
977977

978978
g.writeln('.has_limit = ${node.has_limit},')
979979
g.writeln('.has_offset = ${node.has_offset},')
980+
g.writeln('.has_distinct = ${node.has_distinct},')
980981

981982
if primary_field.name != '' {
982983
g.writeln('.primary = _S("${primary_field.name}"),')

‎vlib/v/parser/orm.v‎

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ fn (mut p Parser) sql_expr() ast.Expr {
2828
// kind := if is_select { ast.SqlExprKind.select_ } else { ast.SqlExprKind.insert }
2929
mut inserted_var := ''
3030
mut is_count := false
31+
mut has_distinct := false
3132
if is_insert {
3233
inserted_var = p.check_name()
3334
p.scope.mark_var_as_used(inserted_var)
@@ -36,7 +37,14 @@ fn (mut p Parser) sql_expr() ast.Expr {
3637
p.error('expecting `into`')
3738
}
3839
} else if is_select {
39-
is_count = p.check_name() == 'count'
40+
n := p.check_name()
41+
if n == 'distinct' {
42+
has_distinct = true
43+
n2 := p.check_name()
44+
is_count = n2 == 'count'
45+
} else {
46+
is_count = n == 'count'
47+
}
4048
}
4149
mut typ := ast.void_type
4250

@@ -128,6 +136,7 @@ fn (mut p Parser) sql_expr() ast.Expr {
128136
has_order: has_order
129137
order_expr: order_expr
130138
has_desc: has_desc
139+
has_distinct: has_distinct
131140
is_array: if is_count { false } else { true }
132141
is_generated: false
133142
inserted_var: inserted_var

0 commit comments

Comments
 (0)