v2 / vlib / db / mysql / orm.c.v
550 lines · 496 sloc · 14.16 KB · d5578efae67ef01ab3a63866d429b7dacf6c237d
Raw
1module mysql
2
3import orm
4import strconv
5import time
6
7// select is used internally by V's ORM for processing `SELECT ` queries.
8pub fn (db DB) select(config orm.SelectConfig, data orm.QueryData, where orm.QueryData) ![][]orm.Primitive {
9 where_with_tenant := orm.apply_tenant_filter(config.table, where)
10 query := orm.orm_select_gen(config, '`', false, '?', 0, where_with_tenant)
11 mut result := [][]orm.Primitive{}
12 mut stmt := db.init_stmt(query)
13 stmt.prepare()!
14
15 mysql_stmt_bind_query_data(mut stmt, where_with_tenant)!
16 mysql_stmt_bind_query_data(mut stmt, data)!
17
18 if data.data.len > 0 || where_with_tenant.data.len > 0 {
19 stmt.bind_params()!
20 }
21
22 stmt.execute()!
23 metadata := stmt.gen_metadata()
24 fields := stmt.fetch_fields(metadata)
25 num_fields := stmt.get_field_count()
26 mut data_pointers := []&u8{cap: int(num_fields)}
27
28 // Allocate memory for each column.
29 for i in 0 .. num_fields {
30 field := unsafe { fields[i] }
31 match unsafe { FieldType(field.type) } {
32 .type_tiny {
33 data_pointers << unsafe { malloc(1) }
34 }
35 .type_short {
36 data_pointers << unsafe { malloc(2) }
37 }
38 .type_long, .type_float {
39 data_pointers << unsafe { malloc(4) }
40 }
41 .type_longlong, .type_double {
42 data_pointers << unsafe { malloc(8) }
43 }
44 .type_time, .type_date, .type_datetime, .type_time2, .type_datetime2, .type_timestamp {
45 data_pointers << unsafe { malloc(sizeof(C.MYSQL_TIME)) }
46 }
47 .type_decimal, .type_newdecimal, .type_string, .type_var_string, .type_blob,
48 .type_tiny_blob, .type_medium_blob, .type_long_blob {
49 // Memory will be allocated later dynamically depending on the length of the value.
50 data_pointers << &u8(unsafe { nil })
51 }
52 else {
53 return error('\'${unsafe { FieldType(field.type) }}\' is not yet implemented. Please create a new issue at https://github.com/vlang/v/issues/new')
54 }
55 }
56 }
57
58 mut lengths := []u32{len: int(num_fields), init: 0}
59 mut is_null := []bool{len: int(num_fields)}
60 stmt.bind_res(fields, data_pointers, lengths, is_null, num_fields)
61
62 mut types := config.types.clone()
63 mut field_types := []FieldType{}
64 if config.aggregate_kind == .count {
65 types = [orm.type_idx['u64']]
66 }
67
68 // Map stores column indexes and their binds in order to extract values
69 // for these columns separately, with individual memory allocation for each value.
70 mut string_binds_map := map[int]C.MYSQL_BIND{}
71
72 for i, mut mysql_bind in stmt.res {
73 field := unsafe { fields[i] }
74 field_type := unsafe { FieldType(field.type) }
75 field_types << field_type
76
77 match field_type {
78 .type_decimal, .type_newdecimal, .type_string, .type_var_string, .type_blob,
79 .type_tiny_blob, .type_medium_blob, .type_long_blob {
80 if field_type in [.type_decimal, .type_newdecimal] {
81 mysql_bind.buffer_type = C.MYSQL_TYPE_STRING
82 }
83 string_binds_map[i] = mysql_bind
84 }
85 .type_long {
86 mysql_bind.buffer_type = C.MYSQL_TYPE_LONG
87 }
88 .type_time, .type_date, .type_datetime, .type_timestamp {
89 // FIXME: Allocate memory for blobs dynamically.
90 mysql_bind.buffer_type = C.MYSQL_TYPE_BLOB
91 mysql_bind.buffer_length = FieldType.type_blob.get_len()
92 }
93 else {}
94 }
95 }
96
97 stmt.bind_result_buffer()!
98 stmt.store_result()!
99
100 for {
101 // Fetch every row from the `select` result.
102 status := stmt.fetch_stmt()!
103 is_error := status == 1
104 are_no_rows_to_fetch := status == mysql_no_data
105
106 if is_error || are_no_rows_to_fetch {
107 break
108 }
109
110 // Fetch columns that should be allocated dynamically.
111 for index, mut bind in string_binds_map {
112 string_length := lengths[index] + 1
113 data_pointers[index] = unsafe { malloc(string_length) }
114 bind.buffer = data_pointers[index]
115 bind.buffer_length = string_length
116 bind.length = unsafe { nil }
117
118 stmt.fetch_column(bind, index)!
119 }
120
121 mut row := data_pointers_to_primitives(is_null, data_pointers, types, field_types)!
122 if config.aggregate_kind == .count && row.len > 0 {
123 count_value := row[0]
124 row[0] = match count_value {
125 u64 { orm.Primitive(int(count_value)) }
126 i64 { orm.Primitive(int(count_value)) }
127 int { count_value }
128 else { count_value }
129 }
130 }
131 result << row
132 }
133
134 stmt.close()!
135
136 return result
137}
138
139// insert is used internally by V's ORM for processing `INSERT ` queries
140pub fn (db DB) insert(table orm.Table, data orm.QueryData) ! {
141 mut converted_primitive_array := db.convert_query_data_to_primitives(table.name, data)!
142
143 converted_primitive_data := orm.QueryData{
144 fields: data.fields
145 data: converted_primitive_array
146 types: data.types
147 parentheses: data.parentheses
148 kinds: data.kinds
149 auto_fields: data.auto_fields
150 is_and: data.is_and
151 batch_rows: data.batch_rows
152 batch_key: data.batch_key
153 }
154
155 query, converted_data := orm.orm_stmt_gen(.default, table, '`', .insert, false, '?', 1,
156 converted_primitive_data, orm.QueryData{})
157 mysql_stmt_worker(db, query, converted_data, orm.QueryData{})!
158}
159
160// update is used internally by V's ORM for processing `UPDATE ` queries
161pub fn (db DB) update(table orm.Table, data orm.QueryData, where orm.QueryData) ! {
162 where_with_tenant := orm.apply_tenant_filter(table, where)
163 query, _ := orm.orm_stmt_gen(.default, table, '`', .update, false, '?', 1, data,
164 where_with_tenant)
165 mysql_stmt_worker(db, query, data, where_with_tenant)!
166}
167
168// delete is used internally by V's ORM for processing `DELETE ` queries
169pub fn (db DB) delete(table orm.Table, where orm.QueryData) ! {
170 where_with_tenant := orm.apply_tenant_filter(table, where)
171 query, _ := orm.orm_stmt_gen(.default, table, '`', .delete, false, '?', 1, orm.QueryData{},
172 where_with_tenant)
173 mysql_stmt_worker(db, query, orm.QueryData{}, where_with_tenant)!
174}
175
176// last_id is used internally by V's ORM for post-processing `INSERT ` queries
177pub fn (db DB) last_id() int {
178 query := 'SELECT last_insert_id();'
179 id := db.query(query) or { return 0 }
180
181 return id.rows()[0].vals[0].int()
182}
183
184// create is used internally by V's ORM for processing table creation queries (DDL)
185pub fn (db DB) create(table orm.Table, fields []orm.TableField) ! {
186 query := orm.orm_table_gen(.mysql, table, '`', true, 0, fields, mysql_type_from_v, false) or {
187 return err
188 }
189 mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
190}
191
192// drop is used internally by V's ORM for processing table destroying queries (DDL)
193pub fn (db DB) drop(table orm.Table) ! {
194 query := 'DROP TABLE `${table.name}`;'
195 mysql_stmt_worker(db, query, orm.QueryData{}, orm.QueryData{})!
196}
197
198// orm_begin starts a transaction for ORM helpers.
199pub fn (mut db DB) orm_begin() ! {
200 db.begin()!
201}
202
203// orm_commit commits a transaction for ORM helpers.
204pub fn (mut db DB) orm_commit() ! {
205 db.commit()!
206}
207
208// orm_rollback rolls back a transaction for ORM helpers.
209pub fn (mut db DB) orm_rollback() ! {
210 db.rollback()!
211}
212
213// orm_savepoint creates a savepoint for ORM helpers.
214pub fn (mut db DB) orm_savepoint(name string) ! {
215 db.savepoint(name)!
216}
217
218// orm_rollback_to rolls back to a savepoint for ORM helpers.
219pub fn (mut db DB) orm_rollback_to(name string) ! {
220 db.rollback_to(name)!
221}
222
223// orm_release_savepoint releases a savepoint for ORM helpers.
224pub fn (mut db DB) orm_release_savepoint(name string) ! {
225 db.release_savepoint(name)!
226}
227
228// mysql_stmt_worker executes the `query` with the provided `data` and `where` parameters
229// without returning the result.
230// This is commonly used for `INSERT`, `UPDATE`, `CREATE`, `DROP`, and `DELETE` queries.
231fn mysql_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ! {
232 mut stmt := db.init_stmt(query)
233 stmt.prepare()!
234
235 mysql_stmt_bind_query_data(mut stmt, data)!
236 mysql_stmt_bind_query_data(mut stmt, where)!
237
238 if data.data.len > 0 || where.data.len > 0 {
239 stmt.bind_params()!
240 }
241
242 stmt.execute()!
243 stmt.close()!
244}
245
246// mysql_stmt_bind_query_data binds all the fields of `q` to the `stmt`.
247fn mysql_stmt_bind_query_data(mut stmt Stmt, d orm.QueryData) ! {
248 for data in d.data {
249 stmt_bind_primitive(mut stmt, data)
250 }
251}
252
253fn stmt_bind_array[T](mut stmt Stmt, data []T) {
254 for element in data {
255 stmt_bind_primitive(mut stmt, orm.Primitive(element))
256 }
257}
258
259// stmt_bind_primitive binds the `data` to the `stmt`.
260fn stmt_bind_primitive(mut stmt Stmt, data orm.Primitive) {
261 match data {
262 bool {
263 stmt.bind_bool(&data)
264 }
265 i8 {
266 stmt.bind_i8(&data)
267 }
268 i16 {
269 stmt.bind_i16(&data)
270 }
271 int {
272 stmt.bind_int(&data)
273 }
274 i64 {
275 stmt.bind_i64(&data)
276 }
277 u8 {
278 stmt.bind_u8(&data)
279 }
280 u16 {
281 stmt.bind_u16(&data)
282 }
283 u32 {
284 stmt.bind_u32(&data)
285 }
286 u64 {
287 stmt.bind_u64(&data)
288 }
289 f32 {
290 stmt.bind_f32(unsafe { &f32(&data) })
291 }
292 f64 {
293 stmt.bind_f64(unsafe { &f64(&data) })
294 }
295 string {
296 stmt.bind_text(data)
297 }
298 time.Time {
299 unix := int(data.unix())
300 stmt_bind_primitive(mut stmt, unix)
301 }
302 orm.InfixType {
303 stmt_bind_primitive(mut stmt, data.right)
304 }
305 orm.Null {
306 stmt.bind_null()
307 }
308 []orm.Primitive {
309 stmt_bind_array(mut stmt, data)
310 }
311 []bool {
312 stmt_bind_array(mut stmt, data)
313 }
314 []f32 {
315 stmt_bind_array(mut stmt, data)
316 }
317 []f64 {
318 stmt_bind_array(mut stmt, data)
319 }
320 []i16 {
321 stmt_bind_array(mut stmt, data)
322 }
323 []i64 {
324 stmt_bind_array(mut stmt, data)
325 }
326 []i8 {
327 stmt_bind_array(mut stmt, data)
328 }
329 []int {
330 stmt_bind_array(mut stmt, data)
331 }
332 []string {
333 stmt_bind_array(mut stmt, data)
334 }
335 []time.Time {
336 stmt_bind_array(mut stmt, data)
337 }
338 []u16 {
339 stmt_bind_array(mut stmt, data)
340 }
341 []u32 {
342 stmt_bind_array(mut stmt, data)
343 }
344 []u64 {
345 stmt_bind_array(mut stmt, data)
346 }
347 []u8 {
348 stmt_bind_array(mut stmt, data)
349 }
350 []orm.InfixType {
351 stmt_bind_array(mut stmt, data)
352 }
353 }
354}
355
356// data_pointers_to_primitives returns an array of `Primitive`
357// cast from `data_pointers` using `types`.
358fn data_pointers_to_primitives(is_null []bool, data_pointers []&u8, types []int, field_types []FieldType) ![]orm.Primitive {
359 mut result := []orm.Primitive{}
360
361 for i, data in data_pointers {
362 mut primitive := orm.Primitive(0)
363 if !is_null[i] {
364 if field_types[i] in [.type_decimal, .type_newdecimal] {
365 decimal_value := unsafe { cstring_to_vstring(&char(data)) }
366 primitive = decimal_string_to_primitive(decimal_value, types[i])!
367 result << primitive
368 continue
369 }
370 match types[i] {
371 orm.type_idx['i8'] {
372 primitive = *(unsafe { &i8(data) })
373 }
374 orm.type_idx['i16'] {
375 primitive = *(unsafe { &i16(data) })
376 }
377 orm.type_idx['int'], orm.serial {
378 primitive = *(unsafe { &int(data) })
379 }
380 orm.type_idx['i64'] {
381 primitive = *(unsafe { &i64(data) })
382 }
383 orm.type_idx['u8'] {
384 primitive = *(unsafe { &u8(data) })
385 }
386 orm.type_idx['u16'] {
387 primitive = *(unsafe { &u16(data) })
388 }
389 orm.type_idx['u32'] {
390 primitive = *(unsafe { &u32(data) })
391 }
392 orm.type_idx['u64'] {
393 primitive = *(unsafe { &u64(data) })
394 }
395 orm.type_idx['f32'] {
396 primitive = *(unsafe { &f32(data) })
397 }
398 orm.type_idx['f64'] {
399 primitive = *(unsafe { &f64(data) })
400 }
401 orm.type_idx['bool'] {
402 primitive = *(unsafe { &bool(data) })
403 }
404 orm.type_string {
405 primitive = unsafe { cstring_to_vstring(&char(data)) }
406 }
407 orm.time_ {
408 match field_types[i] {
409 .type_long {
410 timestamp := *(unsafe { &int(data) })
411 primitive = time.unix(timestamp)
412 }
413 .type_datetime, .type_timestamp {
414 primitive = time.parse(unsafe { cstring_to_vstring(&char(data)) })!
415 }
416 else {}
417 }
418 }
419 orm.enum_ {
420 primitive = *(unsafe { &i64(data) })
421 }
422 else {
423 return error('Unknown type ${types[i]}')
424 }
425 }
426 } else {
427 primitive = orm.Null{}
428 }
429 result << primitive
430 }
431
432 return result
433}
434
435fn decimal_string_to_primitive(value string, typ int) !orm.Primitive {
436 return match typ {
437 orm.type_idx['i8'] {
438 orm.Primitive(strconv.atoi8(value)!)
439 }
440 orm.type_idx['i16'] {
441 orm.Primitive(strconv.atoi16(value)!)
442 }
443 orm.type_idx['int'], orm.serial {
444 orm.Primitive(strconv.atoi(value)!)
445 }
446 orm.type_idx['i64'], orm.enum_ {
447 orm.Primitive(strconv.atoi64(value)!)
448 }
449 orm.type_idx['u8'] {
450 orm.Primitive(strconv.atou8(value)!)
451 }
452 orm.type_idx['u16'] {
453 orm.Primitive(strconv.atou16(value)!)
454 }
455 orm.type_idx['u32'] {
456 orm.Primitive(strconv.atou32(value)!)
457 }
458 orm.type_idx['u64'] {
459 orm.Primitive(strconv.atou64(value)!)
460 }
461 orm.type_idx['f32'] {
462 orm.Primitive(f32(strconv.atof64(value)!))
463 }
464 orm.type_idx['f64'] {
465 orm.Primitive(strconv.atof64(value)!)
466 }
467 else {
468 return error('Unknown decimal target type ${typ}')
469 }
470 }
471}
472
473// mysql_type_from_v converts the V type to the corresponding MySQL type.
474fn mysql_type_from_v(typ int) !string {
475 sql_type := match typ {
476 orm.type_idx['i8'], orm.type_idx['u8'] {
477 'TINYINT'
478 }
479 orm.type_idx['i16'], orm.type_idx['u16'] {
480 'SMALLINT'
481 }
482 orm.type_idx['int'], orm.type_idx['u32'], orm.time_ {
483 'INT'
484 }
485 orm.type_idx['i64'], orm.type_idx['u64'], orm.enum_ {
486 'BIGINT'
487 }
488 orm.type_idx['f32'] {
489 'FLOAT'
490 }
491 orm.type_idx['f64'] {
492 'DOUBLE'
493 }
494 orm.type_string {
495 'TEXT'
496 }
497 orm.serial {
498 'SERIAL'
499 }
500 orm.type_idx['bool'] {
501 'BOOLEAN'
502 }
503 else {
504 return error('Unknown type ${typ}')
505 }
506 }
507
508 return sql_type
509}
510
511// convert_query_data_to_primitives converts the `data` representing the `QueryData`
512// into an array of `Primitive`.
513fn (db DB) convert_query_data_to_primitives(table string, data orm.QueryData) ![]orm.Primitive {
514 mut column_type_map := db.get_table_column_type_map(table)!
515 mut converted_data := []orm.Primitive{}
516 if data.fields.len == 0 {
517 return converted_data
518 }
519
520 for i, primitive in data.data {
521 field := data.fields[i % data.fields.len]
522 if primitive.type_name() == 'time.Time' {
523 if column_type_map[field] in ['datetime', 'timestamp'] {
524 converted_data << orm.Primitive((primitive as time.Time).str())
525 } else {
526 converted_data << primitive
527 }
528 } else {
529 converted_data << primitive
530 }
531 }
532
533 return converted_data
534}
535
536// get_table_column_type_map returns a map where the key represents the column name,
537// and the value represents its data type.
538fn (db DB) get_table_column_type_map(table string) !map[string]string {
539 data_type_query := "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '${table}'"
540 mut column_type_map := map[string]string{}
541 results := db.query(data_type_query)!
542
543 for row in results.rows() {
544 column_type_map[row.vals[0]] = row.vals[1]
545 }
546
547 unsafe { results.free() }
548
549 return column_type_map
550}
551