| 1 | // vtest build: present_sqlite3? |
| 2 | import orm |
| 3 | import db.sqlite |
| 4 | import time |
| 5 | |
| 6 | struct TestCustomSqlType { |
| 7 | id int @[primary; sql: serial] |
| 8 | custom string @[sql_type: 'INTEGER'] |
| 9 | custom1 string @[sql_type: 'TEXT'] |
| 10 | custom2 string @[sql_type: 'REAL'] |
| 11 | custom3 string @[sql_type: 'NUMERIC'] |
| 12 | custom4 string |
| 13 | custom5 int |
| 14 | custom6 time.Time |
| 15 | } |
| 16 | |
| 17 | struct TestDefaultAttribute { |
| 18 | id string @[primary; sql: serial] |
| 19 | name string |
| 20 | created_at ?string @[default: 'CURRENT_TIME'] |
| 21 | created_at1 ?string @[default: 'CURRENT_DATE'] |
| 22 | created_at2 ?string @[default: 'CURRENT_TIMESTAMP'] |
| 23 | } |
| 24 | |
| 25 | struct TestDurationAlias { |
| 26 | id int @[primary; sql: serial] |
| 27 | duration time.Duration |
| 28 | } |
| 29 | |
| 30 | struct EntityToTest { |
| 31 | id int @[notnull; sql_type: 'INTEGER'] |
| 32 | smth string @[notnull; sql_type: 'TEXT'] |
| 33 | } |
| 34 | |
| 35 | struct TutorialBlogArticle { |
| 36 | id int @[primary; sql: serial] |
| 37 | title string |
| 38 | text string |
| 39 | } |
| 40 | |
| 41 | struct TutorialBlogApp { |
| 42 | mut: |
| 43 | db sqlite.DB |
| 44 | } |
| 45 | |
| 46 | fn (app &TutorialBlogApp) find_all_tutorial_blog_articles() []TutorialBlogArticle { |
| 47 | return sql app.db { |
| 48 | select from TutorialBlogArticle |
| 49 | } or { panic(err) } |
| 50 | } |
| 51 | |
| 52 | fn test_sqlite_orm() { |
| 53 | mut db := sqlite.connect(':memory:') or { panic(err) } |
| 54 | defer { |
| 55 | db.close() or { panic(err) } |
| 56 | } |
| 57 | table := orm.Table{ |
| 58 | name: 'Test' |
| 59 | } |
| 60 | db.create(table, [ |
| 61 | orm.TableField{ |
| 62 | name: 'id' |
| 63 | typ: typeof[int]().idx |
| 64 | attrs: [ |
| 65 | VAttribute{ |
| 66 | name: 'primary' |
| 67 | }, |
| 68 | VAttribute{ |
| 69 | name: 'sql' |
| 70 | has_arg: true |
| 71 | kind: .plain |
| 72 | arg: 'serial' |
| 73 | }, |
| 74 | ] |
| 75 | }, |
| 76 | orm.TableField{ |
| 77 | name: 'name' |
| 78 | typ: typeof[string]().idx |
| 79 | attrs: [] |
| 80 | }, |
| 81 | orm.TableField{ |
| 82 | name: 'age' |
| 83 | typ: typeof[i64]().idx |
| 84 | }, |
| 85 | ]) or { panic(err) } |
| 86 | |
| 87 | db.insert(table, orm.QueryData{ |
| 88 | fields: ['name', 'age'] |
| 89 | data: [orm.string_to_primitive('Louis'), orm.i64_to_primitive(100)] |
| 90 | }) or { panic(err) } |
| 91 | |
| 92 | res := db.select(orm.SelectConfig{ |
| 93 | table: table |
| 94 | has_where: true |
| 95 | fields: ['id', 'name', 'age'] |
| 96 | types: [typeof[int]().idx, typeof[string]().idx, typeof[i64]().idx] |
| 97 | }, orm.QueryData{}, orm.QueryData{ |
| 98 | fields: ['name', 'age'] |
| 99 | data: [orm.Primitive('Louis'), i64(100)] |
| 100 | types: [typeof[string]().idx, typeof[i64]().idx] |
| 101 | is_and: [true, true] |
| 102 | kinds: [.eq, .eq] |
| 103 | }) or { panic(err) } |
| 104 | |
| 105 | id := res[0][0] |
| 106 | name := res[0][1] |
| 107 | age := res[0][2] |
| 108 | |
| 109 | assert id is int |
| 110 | if id is int { |
| 111 | assert id == 1 |
| 112 | } |
| 113 | |
| 114 | assert name is string |
| 115 | if name is string { |
| 116 | assert name == 'Louis' |
| 117 | } |
| 118 | |
| 119 | assert age is i64 |
| 120 | if age is i64 { |
| 121 | assert age == 100 |
| 122 | } |
| 123 | |
| 124 | /** test orm sql type |
| 125 | * - verify if all type create by attribute sql_type has created |
| 126 | */ |
| 127 | |
| 128 | sql db { |
| 129 | create table TestCustomSqlType |
| 130 | }! |
| 131 | |
| 132 | mut result_custom_sql := db.exec(' |
| 133 | pragma table_info(TestCustomSqlType); |
| 134 | ')! |
| 135 | |
| 136 | mut table_info_types_results := []string{} |
| 137 | information_schema_custom_sql := ['INTEGER', 'INTEGER', 'TEXT', 'REAL', 'NUMERIC', 'TEXT', |
| 138 | 'INTEGER', 'INTEGER'] |
| 139 | |
| 140 | for data_type in result_custom_sql { |
| 141 | table_info_types_results << data_type.vals[2] |
| 142 | } |
| 143 | assert table_info_types_results == information_schema_custom_sql |
| 144 | |
| 145 | sql db { |
| 146 | drop table TestCustomSqlType |
| 147 | }! |
| 148 | |
| 149 | /** test default attribute |
| 150 | */ |
| 151 | |
| 152 | sql db { |
| 153 | create table TestDefaultAttribute |
| 154 | }! |
| 155 | |
| 156 | mut result_default_sql := db.exec(' |
| 157 | pragma table_info(TestDefaultAttribute); |
| 158 | ')! |
| 159 | |
| 160 | mut information_schema_data_types_results := []string{} |
| 161 | information_schema_default_sql := ['', '', 'CURRENT_TIME', 'CURRENT_DATE', 'CURRENT_TIMESTAMP'] |
| 162 | |
| 163 | for data_type in result_default_sql { |
| 164 | information_schema_data_types_results << data_type.vals[4] |
| 165 | } |
| 166 | assert information_schema_data_types_results == information_schema_default_sql |
| 167 | |
| 168 | test_default_attribute := TestDefaultAttribute{ |
| 169 | name: 'Hitalo' |
| 170 | } |
| 171 | |
| 172 | sql db { |
| 173 | insert test_default_attribute into TestDefaultAttribute |
| 174 | }! |
| 175 | |
| 176 | test_default_attributes := sql db { |
| 177 | select from TestDefaultAttribute limit 1 |
| 178 | }! |
| 179 | |
| 180 | result_test_default_attribute := test_default_attributes.first() |
| 181 | assert result_test_default_attribute.name == 'Hitalo' |
| 182 | assert test_default_attribute.created_at or { '' } == '' |
| 183 | assert test_default_attribute.created_at1 or { '' } == '' |
| 184 | assert test_default_attribute.created_at2 or { '' } == '' |
| 185 | assert result_test_default_attribute.created_at or { '' }.len == 8 // HH:MM:SS |
| 186 | assert result_test_default_attribute.created_at1 or { '' }.len == 10 // YYYY-MM-DD |
| 187 | assert result_test_default_attribute.created_at2 or { '' }.len == 19 // YYYY-MM-DD HH:MM:SS |
| 188 | |
| 189 | sql db { |
| 190 | drop table TestDefaultAttribute |
| 191 | }! |
| 192 | } |
| 193 | |
| 194 | fn test_get_affected_rows_count() { |
| 195 | mut db := sqlite.connect(':memory:') or { panic(err) } |
| 196 | defer { |
| 197 | db.close() or { panic(err) } |
| 198 | } |
| 199 | |
| 200 | db.exec('create table EntityToTest( |
| 201 | id integer not null constraint tbl_pk primary key, |
| 202 | smth integer |
| 203 | );')! |
| 204 | |
| 205 | fst := EntityToTest{ |
| 206 | id: 1 |
| 207 | smth: '1' |
| 208 | } |
| 209 | |
| 210 | sql db { |
| 211 | insert fst into EntityToTest |
| 212 | } or { panic('first insert failed') } |
| 213 | |
| 214 | assert db.get_affected_rows_count() == 1 |
| 215 | |
| 216 | snd := EntityToTest{ |
| 217 | id: 1 |
| 218 | smth: '2' |
| 219 | } |
| 220 | |
| 221 | mut sndfailed := false |
| 222 | sql db { |
| 223 | insert snd into EntityToTest |
| 224 | } or { sndfailed = true } |
| 225 | |
| 226 | assert db.get_affected_rows_count() == 0 |
| 227 | assert sndfailed |
| 228 | |
| 229 | all := sql db { |
| 230 | select from EntityToTest |
| 231 | }! |
| 232 | assert 1 == all.len |
| 233 | |
| 234 | sql db { |
| 235 | update EntityToTest set smth = '2' where id == 1 |
| 236 | }! |
| 237 | assert db.get_affected_rows_count() == 1 |
| 238 | |
| 239 | sql db { |
| 240 | update EntityToTest set smth = '2' where id == 2 |
| 241 | }! |
| 242 | assert db.get_affected_rows_count() == 0 |
| 243 | |
| 244 | sql db { |
| 245 | delete from EntityToTest where id == 2 |
| 246 | }! |
| 247 | assert db.get_affected_rows_count() == 0 |
| 248 | |
| 249 | sql db { |
| 250 | delete from EntityToTest where id == 1 |
| 251 | }! |
| 252 | assert db.get_affected_rows_count() == 1 |
| 253 | } |
| 254 | |
| 255 | fn test_sqlite_orm_supports_time_duration_alias_fields() { |
| 256 | mut db := sqlite.connect(':memory:') or { panic(err) } |
| 257 | defer { |
| 258 | db.close() or { panic(err) } |
| 259 | } |
| 260 | |
| 261 | sql db { |
| 262 | create table TestDurationAlias |
| 263 | }! |
| 264 | |
| 265 | result := db.exec('pragma table_info(TestDurationAlias);')! |
| 266 | assert result.len == 2 |
| 267 | assert result[1].vals[1] == 'duration' |
| 268 | assert result[1].vals[2] == 'INTEGER' |
| 269 | |
| 270 | record := TestDurationAlias{ |
| 271 | duration: 3 * time.second |
| 272 | } |
| 273 | |
| 274 | sql db { |
| 275 | insert record into TestDurationAlias |
| 276 | }! |
| 277 | |
| 278 | rows := sql db { |
| 279 | select from TestDurationAlias limit 1 |
| 280 | }! |
| 281 | assert rows.len == 1 |
| 282 | assert rows[0].duration == 3 * time.second |
| 283 | } |
| 284 | |
| 285 | fn test_sqlite_orm_tutorial_style_select_method() { |
| 286 | mut app := TutorialBlogApp{ |
| 287 | db: sqlite.connect(':memory:') or { panic(err) } |
| 288 | } |
| 289 | defer { |
| 290 | app.db.close() or { panic(err) } |
| 291 | } |
| 292 | |
| 293 | sql app.db { |
| 294 | create table TutorialBlogArticle |
| 295 | }! |
| 296 | |
| 297 | first_article := TutorialBlogArticle{ |
| 298 | title: 'Hello, world!' |
| 299 | text: 'V is great.' |
| 300 | } |
| 301 | second_article := TutorialBlogArticle{ |
| 302 | title: 'Second post.' |
| 303 | text: 'Hm... what should I write about?' |
| 304 | } |
| 305 | |
| 306 | sql app.db { |
| 307 | insert first_article into TutorialBlogArticle |
| 308 | insert second_article into TutorialBlogArticle |
| 309 | }! |
| 310 | |
| 311 | articles := app.find_all_tutorial_blog_articles() |
| 312 | assert articles.len == 2 |
| 313 | |
| 314 | mut titles := articles.map(it.title) |
| 315 | titles.sort() |
| 316 | assert titles == ['Hello, world!', 'Second post.'] |
| 317 | } |
| 318 | |