v / vlib / orm / orm_generic_where_shadow_test.v
190 lines · 162 sloc · 4.91 KB · c2f124cb6ef05f8bc3d53282f829a58beef9fe80
Raw
1// vtest build: present_sqlite3? && !sanitize-memory-clang
2// vtest vflags: -prod
3// Tests for generic ORM with WHERE clause variable shadowing field names.
4// This tests the fix for the bug where `select from T where status == status`
5// (where `status` is both a field name and a local variable) was incorrectly
6// optimized to `true` by the transformer, resulting in empty WHERE clauses.
7import db.sqlite
8
9pub enum TestStatus as u8 {
10 pending
11 active
12 completed
13}
14
15pub struct GenericRecord[T] {
16pub:
17 id int @[primary; sql: serial]
18 status TestStatus
19 payload T @[sql: '-']
20}
21
22struct TestPayload {
23 value int
24}
25
26// Test that WHERE clause works when variable name shadows field name
27fn test_generic_where_variable_shadows_field() {
28 mut db := sqlite.connect(':memory:') or { panic(err) }
29 defer { db.close() or {} }
30
31 sql db {
32 create table GenericRecord[TestPayload]
33 } or { panic(err) }
34
35 // Insert records with different statuses
36 r1 := GenericRecord[TestPayload]{
37 status: .pending
38 payload: TestPayload{
39 value: 1
40 }
41 }
42 r2 := GenericRecord[TestPayload]{
43 status: .active
44 payload: TestPayload{
45 value: 2
46 }
47 }
48 r3 := GenericRecord[TestPayload]{
49 status: .active
50 payload: TestPayload{
51 value: 3
52 }
53 }
54 r4 := GenericRecord[TestPayload]{
55 status: .completed
56 payload: TestPayload{
57 value: 4
58 }
59 }
60
61 sql db {
62 insert r1 into GenericRecord[TestPayload]
63 insert r2 into GenericRecord[TestPayload]
64 insert r3 into GenericRecord[TestPayload]
65 insert r4 into GenericRecord[TestPayload]
66 } or { panic(err) }
67
68 // Test: variable `status` shadows field `status`
69 // Before the fix, this would return all rows or error because
70 // `status == status` was optimized to `true`
71 status := TestStatus.active
72 results := sql db {
73 select from GenericRecord[TestPayload] where status == status
74 } or { panic(err) }
75
76 assert results.len == 2, 'Expected 2 active records, got ${results.len}'
77
78 // Test with different status values
79 status2 := TestStatus.pending
80 pending_results := sql db {
81 select from GenericRecord[TestPayload] where status == status2
82 } or { panic(err) }
83 assert pending_results.len == 1, 'Expected 1 pending record, got ${pending_results.len}'
84
85 status3 := TestStatus.completed
86 completed_results := sql db {
87 select from GenericRecord[TestPayload] where status == status3
88 } or { panic(err) }
89 assert completed_results.len == 1, 'Expected 1 completed record, got ${completed_results.len}'
90}
91
92// Test generic select helper function with shadowed variable
93fn generic_select_by_status[T](db &sqlite.DB, status TestStatus) ![]GenericRecord[T] {
94 return sql db {
95 select from GenericRecord[T] where status == status
96 }!
97}
98
99fn test_generic_function_where_shadow() {
100 mut db := sqlite.connect(':memory:') or { panic(err) }
101 defer { db.close() or {} }
102
103 sql db {
104 create table GenericRecord[TestPayload]
105 } or { panic(err) }
106
107 r1 := GenericRecord[TestPayload]{
108 status: .pending
109 payload: TestPayload{
110 value: 10
111 }
112 }
113 r2 := GenericRecord[TestPayload]{
114 status: .active
115 payload: TestPayload{
116 value: 20
117 }
118 }
119
120 sql db {
121 insert r1 into GenericRecord[TestPayload]
122 insert r2 into GenericRecord[TestPayload]
123 } or { panic(err) }
124
125 // Call generic function where parameter shadows field name
126 active_records := generic_select_by_status[TestPayload](&db, .active) or { panic(err) }
127 assert active_records.len == 1, 'Expected 1 active record from generic function'
128 assert active_records[0].status == .active
129
130 pending_records := generic_select_by_status[TestPayload](&db, .pending) or { panic(err) }
131 assert pending_records.len == 1, 'Expected 1 pending record from generic function'
132 assert pending_records[0].status == .pending
133}
134
135// Test count with shadowed variable
136fn test_generic_count_where_shadow() {
137 mut db := sqlite.connect(':memory:') or { panic(err) }
138 defer { db.close() or {} }
139
140 sql db {
141 create table GenericRecord[TestPayload]
142 } or { panic(err) }
143
144 for i in 0 .. 5 {
145 r := GenericRecord[TestPayload]{
146 status: if i < 3 { .active } else { .completed }
147 payload: TestPayload{
148 value: i
149 }
150 }
151 sql db {
152 insert r into GenericRecord[TestPayload]
153 } or { panic(err) }
154 }
155
156 status := TestStatus.active
157 count := sql db {
158 select count from GenericRecord[TestPayload] where status == status
159 } or { panic(err) }
160
161 assert count == 3, 'Expected count 3, got ${count}'
162}
163
164// Test that table name doesn't include generic brackets
165fn test_generic_table_name() {
166 mut db := sqlite.connect(':memory:') or { panic(err) }
167 defer { db.close() or {} }
168
169 // This should create table named "GenericRecord", not "GenericRecord[TestPayload]"
170 sql db {
171 create table GenericRecord[TestPayload]
172 } or { panic(err) }
173
174 // Insert and select should work without SQL syntax errors
175 r := GenericRecord[TestPayload]{
176 status: .pending
177 payload: TestPayload{
178 value: 42
179 }
180 }
181 sql db {
182 insert r into GenericRecord[TestPayload]
183 } or { panic(err) }
184
185 results := sql db {
186 select from GenericRecord[TestPayload]
187 } or { panic(err) }
188
189 assert results.len == 1
190}
191