| 1 | // vtest retry: 3 |
| 2 | import orm |
| 3 | import db.sqlite |
| 4 | |
| 5 | struct MockDBState { |
| 6 | mut: |
| 7 | last string |
| 8 | data []orm.Primitive |
| 9 | where []orm.Primitive |
| 10 | } |
| 11 | |
| 12 | struct MockDB { |
| 13 | use_num bool |
| 14 | st &MockDBState = unsafe { nil } |
| 15 | db sqlite.DB |
| 16 | } |
| 17 | |
| 18 | fn MockDB.new() &MockDB { |
| 19 | return &MockDB{ |
| 20 | st: &MockDBState{} |
| 21 | db: sqlite.connect(':memory:') or { panic(err) } |
| 22 | } |
| 23 | } |
| 24 | |
| 25 | fn (db MockDB) select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive { |
| 26 | mut st := db.st |
| 27 | st.last = orm.orm_select_gen(config, '`', false, '?', 5, where) |
| 28 | st.data = data.data |
| 29 | st.where = where.data |
| 30 | return db.db.select(config, data, where) |
| 31 | } |
| 32 | |
| 33 | fn (db MockDB) insert(table orm.Table, data orm.QueryData) ! { |
| 34 | mut st := db.st |
| 35 | last, qdata := |
| 36 | orm.orm_stmt_gen(.sqlite, table, '`', .insert, false, '?', 1, data, orm.QueryData{}) |
| 37 | st.last = last |
| 38 | st.data = qdata.data |
| 39 | st.where = []orm.Primitive{} |
| 40 | return db.db.insert(table, data) |
| 41 | } |
| 42 | |
| 43 | fn (db MockDB) update(table orm.Table, data orm.QueryData, where orm.QueryData) ! { |
| 44 | mut st := db.st |
| 45 | st.last, _ = orm.orm_stmt_gen(.sqlite, table, '`', .update, false, '?', 1, data, where) |
| 46 | st.data = data.data |
| 47 | st.where = where.data |
| 48 | return db.db.update(table, data, where) |
| 49 | } |
| 50 | |
| 51 | fn (db MockDB) delete(table orm.Table, where orm.QueryData) ! { |
| 52 | mut st := db.st |
| 53 | st.last, _ = orm.orm_stmt_gen(.sqlite, table, '`', .delete, false, '?', 1, orm.QueryData{}, |
| 54 | where) |
| 55 | return db.db.delete(table, where) |
| 56 | } |
| 57 | |
| 58 | const typ_to_typename = { |
| 59 | typeof[i8]().idx: 'i8' |
| 60 | typeof[i16]().idx: 'i16' |
| 61 | typeof[int]().idx: 'int' |
| 62 | typeof[i64]().idx: 'i64' |
| 63 | typeof[u8]().idx: 'u8' |
| 64 | typeof[u16]().idx: 'u16' |
| 65 | typeof[u32]().idx: 'u32' |
| 66 | typeof[u64]().idx: 'u64' |
| 67 | typeof[f32]().idx: 'f32' |
| 68 | typeof[f64]().idx: 'f64' |
| 69 | typeof[string]().idx: 'string' |
| 70 | typeof[bool]().idx: 'bool' |
| 71 | orm.serial: 'serial' |
| 72 | orm.time_: 'time' |
| 73 | orm.enum_: 'enum' |
| 74 | } |
| 75 | |
| 76 | fn mock_type_from_v(typ int) !string { |
| 77 | return if typ in typ_to_typename { |
| 78 | '${typ_to_typename[typ]}-type' |
| 79 | } else { |
| 80 | error('unknown type ${typ}') |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | fn (db MockDB) create(table orm.Table, fields []orm.TableField) ! { |
| 85 | mut st := db.st |
| 86 | st.last = orm.orm_table_gen(.sqlite, table, '`', true, 0, fields, mock_type_from_v, false)! |
| 87 | return db.db.create(table, fields) |
| 88 | } |
| 89 | |
| 90 | fn (db MockDB) drop(table orm.Table) ! { |
| 91 | return db.db.drop(table) |
| 92 | } |
| 93 | |
| 94 | fn (db MockDB) last_id() int { |
| 95 | return db.db.last_id() |
| 96 | } |
| 97 | |
| 98 | // -- |
| 99 | |
| 100 | @[table: 'foo'] |
| 101 | struct Foo { |
| 102 | mut: |
| 103 | id u64 @[primary; sql: serial] |
| 104 | a string |
| 105 | b string @[default: '"yes"'] |
| 106 | c ?string |
| 107 | d ?string = 'hi' |
| 108 | e int |
| 109 | f int @[default: 33] |
| 110 | g ?int |
| 111 | h ?int = 55 |
| 112 | } |
| 113 | |
| 114 | @[unique_key: 'role_id, api_id, source_type, source_id'] |
| 115 | @[table: 'core_role_api'] |
| 116 | struct CoreRoleApi { |
| 117 | role_id string |
| 118 | api_id string |
| 119 | source_type string |
| 120 | source_id string |
| 121 | } |
| 122 | |
| 123 | fn test_struct_unique_key_attribute() { |
| 124 | db := MockDB.new() |
| 125 | |
| 126 | sql db { |
| 127 | create table CoreRoleApi |
| 128 | }! |
| 129 | assert db.st.last == 'CREATE TABLE IF NOT EXISTS `core_role_api` (`role_id` string-type NOT NULL, `api_id` string-type NOT NULL, `source_type` string-type NOT NULL, `source_id` string-type NOT NULL, UNIQUE(`role_id`, `api_id`, `source_type`, `source_id`));' |
| 130 | } |
| 131 | |
| 132 | fn test_option_struct_fields_and_none() { |
| 133 | db := MockDB.new() |
| 134 | |
| 135 | sql db { |
| 136 | create table Foo |
| 137 | }! |
| 138 | assert db.st.last == 'CREATE TABLE IF NOT EXISTS `foo` (`id` serial-type NOT NULL, `a` string-type NOT NULL, `b` string-type DEFAULT "yes" NOT NULL, `c` string-type, `d` string-type, `e` int-type NOT NULL, `f` int-type DEFAULT 33 NOT NULL, `g` int-type, `h` int-type, PRIMARY KEY(`id`));' |
| 139 | |
| 140 | _ := sql db { |
| 141 | select from Foo where e > 5 && c is none && c !is none && h == 2 |
| 142 | }! |
| 143 | assert db.st.last == 'SELECT `id`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` FROM `foo` WHERE `e` > ? AND `c` IS NULL AND `c` IS NOT NULL AND `h` = ?;' |
| 144 | assert db.st.data.len == 0 |
| 145 | assert db.st.where.len == 2 |
| 146 | assert db.st.where == [orm.Primitive(int(5)), orm.Primitive(int(2))] |
| 147 | |
| 148 | foo := Foo{} |
| 149 | sql db { |
| 150 | insert foo into Foo |
| 151 | }! |
| 152 | assert db.st.last == 'INSERT INTO `foo` (`a`, `c`, `d`, `e`, `g`, `h`) VALUES (?, ?, ?, ?, ?, ?);' |
| 153 | assert db.st.data.len == 6 |
| 154 | assert db.st.data == [orm.Primitive(string('')), orm.Null{}, orm.Primitive(string('hi')), int(0), |
| 155 | orm.Null{}, int(55)] |
| 156 | id := db.last_id() |
| 157 | |
| 158 | res1 := sql db { |
| 159 | select from Foo where id == id |
| 160 | }! |
| 161 | assert db.st.last == 'SELECT `id`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` FROM `foo` WHERE `id` = ?;' |
| 162 | assert db.st.data.len == 0 |
| 163 | assert db.st.where.len == 1 |
| 164 | assert db.st.where == [orm.Primitive(int(id))] |
| 165 | assert res1.len == 1 |
| 166 | assert res1[0] == Foo{ |
| 167 | id: 1 |
| 168 | a: '' |
| 169 | b: 'yes' |
| 170 | c: none |
| 171 | d: 'hi' |
| 172 | e: 0 |
| 173 | f: 33 |
| 174 | g: none |
| 175 | h: 55 |
| 176 | } |
| 177 | |
| 178 | sql db { |
| 179 | update Foo set c = 'yo', d = none, g = 44, h = none where id == id |
| 180 | }! |
| 181 | assert db.st.last == 'UPDATE `foo` SET `c` = ?, `d` = ?, `g` = ?, `h` = ? WHERE `id` = ?;' |
| 182 | assert db.st.data.len == 4 |
| 183 | assert db.st.data == [orm.Primitive(string('yo')), orm.Null{}, int(44), orm.Null{}] |
| 184 | assert db.st.where.len == 1 |
| 185 | assert db.st.where == [orm.Primitive(int(id))] |
| 186 | |
| 187 | res2 := sql db { |
| 188 | select from Foo where id == id |
| 189 | }! |
| 190 | assert db.st.last == 'SELECT `id`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` FROM `foo` WHERE `id` = ?;' |
| 191 | assert db.st.data.len == 0 |
| 192 | assert db.st.where.len == 1 |
| 193 | assert db.st.where == [orm.Primitive(int(id))] |
| 194 | assert res2.len == 1 |
| 195 | assert res2[0] == Foo{ |
| 196 | id: 1 |
| 197 | a: '' |
| 198 | b: 'yes' |
| 199 | c: 'yo' |
| 200 | d: none |
| 201 | e: 0 |
| 202 | f: 33 |
| 203 | g: 44 |
| 204 | h: none |
| 205 | } |
| 206 | |
| 207 | _ := sql db { |
| 208 | select from Foo where d == nil && c != nil && a == nil |
| 209 | }! |
| 210 | assert db.st.last == 'SELECT `id`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` FROM `foo` WHERE `d` IS NULL AND `c` IS NOT NULL AND `a` IS NULL;' |
| 211 | assert db.st.data.len == 0 |
| 212 | assert db.st.where.len == 0 |
| 213 | |
| 214 | assert sql db { |
| 215 | select count from Foo where a == 'yo' |
| 216 | }! == 0 |
| 217 | assert sql db { |
| 218 | select count from Foo where b == 'yes' |
| 219 | }! == 1 |
| 220 | assert sql db { |
| 221 | select count from Foo where d == 'yo' |
| 222 | }! == 0 |
| 223 | assert sql db { |
| 224 | select count from Foo where f == 33 |
| 225 | }! == 1 |
| 226 | assert sql db { |
| 227 | select count from Foo where c == 'yo' |
| 228 | }! == 1 |
| 229 | assert sql db { |
| 230 | select count from Foo where a == '' |
| 231 | }! == 1 |
| 232 | assert sql db { |
| 233 | select count from Foo where d == '' |
| 234 | }! == 0 |
| 235 | assert sql db { |
| 236 | select count from Foo where c == '' |
| 237 | }! == 0 |
| 238 | assert sql db { |
| 239 | select count from Foo where a == nil |
| 240 | }! == 0 |
| 241 | assert sql db { |
| 242 | select count from Foo where d == nil |
| 243 | }! == 1 |
| 244 | assert sql db { |
| 245 | select count from Foo where c != nil |
| 246 | }! == 1 |
| 247 | assert sql db { |
| 248 | select count from Foo where a is none |
| 249 | }! == 0 |
| 250 | assert sql db { |
| 251 | select count from Foo where d is none |
| 252 | }! == 1 |
| 253 | assert sql db { |
| 254 | select count from Foo where c is none |
| 255 | }! == 0 |
| 256 | assert sql db { |
| 257 | select count from Foo where a !is none |
| 258 | }! == 1 |
| 259 | assert sql db { |
| 260 | select count from Foo where d !is none |
| 261 | }! == 0 |
| 262 | assert sql db { |
| 263 | select count from Foo where c !is none |
| 264 | }! == 1 |
| 265 | } |
| 266 | |
| 267 | struct Bar { |
| 268 | id u64 @[primary; sql: serial] |
| 269 | name ?string |
| 270 | age int |
| 271 | } |
| 272 | |
| 273 | fn update_bar1(db MockDB, id u64, name ?string) ! { |
| 274 | foo := 66 |
| 275 | sql db { |
| 276 | update Bar set name = name, age = age + 3 + foo where id == id |
| 277 | }! |
| 278 | } |
| 279 | |
| 280 | fn update_bar2(db MockDB, name ?string, new_name ?string) ! { |
| 281 | sql db { |
| 282 | update Bar set name = new_name where name == name |
| 283 | }! |
| 284 | } |
| 285 | |
| 286 | type NameFn = fn () ?string |
| 287 | |
| 288 | fn update_bar3(db MockDB, name_fn NameFn, new_name string) ! { |
| 289 | sql db { |
| 290 | update Bar set name = new_name where name == name_fn() |
| 291 | }! |
| 292 | } |
| 293 | |
| 294 | fn test_inserting_passed_optionals() { |
| 295 | db := MockDB.new() |
| 296 | |
| 297 | entry1 := Bar{} |
| 298 | entry2 := Bar{ |
| 299 | name: 'Alice' |
| 300 | age: 55 |
| 301 | } |
| 302 | entry3 := Bar{ |
| 303 | name: 'Bob' |
| 304 | age: 66 |
| 305 | } |
| 306 | sql db { |
| 307 | create table Bar |
| 308 | insert entry1 into Bar |
| 309 | insert entry2 into Bar |
| 310 | insert entry3 into Bar |
| 311 | }! |
| 312 | |
| 313 | update_bar1(db, 2, none)! |
| 314 | update_bar1(db, 1, 'hi')! |
| 315 | |
| 316 | res1 := sql db { |
| 317 | select from Bar |
| 318 | }! |
| 319 | assert res1.len == 3 |
| 320 | assert res1[0].name or { '' } == 'hi' |
| 321 | assert res1[1].name == none |
| 322 | assert res1[2].name or { '' } == 'Bob' |
| 323 | |
| 324 | update_bar2(db, none, 'xxx')! // no effect (select using "is none", not "== none") |
| 325 | update_bar2(db, 'hi', none)! |
| 326 | |
| 327 | res2 := sql db { |
| 328 | select from Bar |
| 329 | }! |
| 330 | assert res2.len == 3 |
| 331 | assert res2[0].name == none |
| 332 | assert res2[1].name == none |
| 333 | assert res2[2].name or { '' } == 'Bob' |
| 334 | |
| 335 | update_bar3(db, fn () ?string { |
| 336 | return none // no effect (select using "is none", not "== none") |
| 337 | }, 'yyy')! |
| 338 | update_bar3(db, fn () ?string { |
| 339 | return 'Bob' |
| 340 | }, 'www')! |
| 341 | |
| 342 | res3 := sql db { |
| 343 | select from Bar |
| 344 | }! |
| 345 | assert res3.len == 3 |
| 346 | assert res3[0].name == none |
| 347 | assert res3[1].name == none |
| 348 | assert res3[2].name or { '' } == 'www' |
| 349 | } |
| 350 | |
| 351 | fn test_distinct_select() { |
| 352 | db := MockDB.new() |
| 353 | |
| 354 | sql db { |
| 355 | create table Foo |
| 356 | }! |
| 357 | |
| 358 | _ := sql db { |
| 359 | select distinct from Foo |
| 360 | }! |
| 361 | assert db.st.last == 'SELECT DISTINCT `id`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` FROM `foo`;' |
| 362 | |
| 363 | _ := sql db { |
| 364 | select distinct from Foo where e > 5 |
| 365 | }! |
| 366 | assert db.st.last == 'SELECT DISTINCT `id`, `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h` FROM `foo` WHERE `e` > ?;' |
| 367 | } |
| 368 | |