v4 / vlib / db / mysql / orm.c.v
580 lines · 524 sloc · 14.94 KB · f581cbb1c964f89133439f4e303136998ddcfcfb
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// execute runs a raw SQL query and returns result rows as driver-agnostic orm.Row values,
199// with column names populated from the result metadata.
200pub fn (db DB) execute(query string) ![]orm.Row {
201 mut guard := db.acquire_connection_guard()!
202 defer {
203 guard.release()
204 }
205 if C.mysql_query(guard.conn, query.str) != 0 {
206 throw_mysql_error_for_conn(guard.conn)!
207 }
208
209 result := C.mysql_store_result(guard.conn)
210 if result == unsafe { nil } {
211 if get_errno(guard.conn) != 0 {
212 throw_mysql_error_for_conn(guard.conn)!
213 }
214 return []orm.Row{}
215 } else {
216 res := Result{result}
217 defer { unsafe { res.free() } }
218 fields := res.fields()
219 mut names := []string{}
220 for f in fields {
221 names << f.name
222 }
223 rows := res.rows()
224 return rows.map(orm.Row{ vals: it.vals, names: names })
225 }
226}
227
228// orm_begin starts a transaction for ORM helpers.
229pub fn (mut db DB) orm_begin() ! {
230 db.begin()!
231}
232
233// orm_commit commits a transaction for ORM helpers.
234pub fn (mut db DB) orm_commit() ! {
235 db.commit()!
236}
237
238// orm_rollback rolls back a transaction for ORM helpers.
239pub fn (mut db DB) orm_rollback() ! {
240 db.rollback()!
241}
242
243// orm_savepoint creates a savepoint for ORM helpers.
244pub fn (mut db DB) orm_savepoint(name string) ! {
245 db.savepoint(name)!
246}
247
248// orm_rollback_to rolls back to a savepoint for ORM helpers.
249pub fn (mut db DB) orm_rollback_to(name string) ! {
250 db.rollback_to(name)!
251}
252
253// orm_release_savepoint releases a savepoint for ORM helpers.
254pub fn (mut db DB) orm_release_savepoint(name string) ! {
255 db.release_savepoint(name)!
256}
257
258// mysql_stmt_worker executes the `query` with the provided `data` and `where` parameters
259// without returning the result.
260// This is commonly used for `INSERT`, `UPDATE`, `CREATE`, `DROP`, and `DELETE` queries.
261fn mysql_stmt_worker(db DB, query string, data orm.QueryData, where orm.QueryData) ! {
262 mut stmt := db.init_stmt(query)
263 stmt.prepare()!
264
265 mysql_stmt_bind_query_data(mut stmt, data)!
266 mysql_stmt_bind_query_data(mut stmt, where)!
267
268 if data.data.len > 0 || where.data.len > 0 {
269 stmt.bind_params()!
270 }
271
272 stmt.execute()!
273 stmt.close()!
274}
275
276// mysql_stmt_bind_query_data binds all the fields of `q` to the `stmt`.
277fn mysql_stmt_bind_query_data(mut stmt Stmt, d orm.QueryData) ! {
278 for data in d.data {
279 stmt_bind_primitive(mut stmt, data)
280 }
281}
282
283fn stmt_bind_array[T](mut stmt Stmt, data []T) {
284 for element in data {
285 stmt_bind_primitive(mut stmt, orm.Primitive(element))
286 }
287}
288
289// stmt_bind_primitive binds the `data` to the `stmt`.
290fn stmt_bind_primitive(mut stmt Stmt, data orm.Primitive) {
291 match data {
292 bool {
293 stmt.bind_bool(&data)
294 }
295 i8 {
296 stmt.bind_i8(&data)
297 }
298 i16 {
299 stmt.bind_i16(&data)
300 }
301 int {
302 stmt.bind_int(&data)
303 }
304 i64 {
305 stmt.bind_i64(&data)
306 }
307 u8 {
308 stmt.bind_u8(&data)
309 }
310 u16 {
311 stmt.bind_u16(&data)
312 }
313 u32 {
314 stmt.bind_u32(&data)
315 }
316 u64 {
317 stmt.bind_u64(&data)
318 }
319 f32 {
320 stmt.bind_f32(unsafe { &f32(&data) })
321 }
322 f64 {
323 stmt.bind_f64(unsafe { &f64(&data) })
324 }
325 string {
326 stmt.bind_text(data)
327 }
328 time.Time {
329 unix := int(data.unix())
330 stmt_bind_primitive(mut stmt, unix)
331 }
332 orm.InfixType {
333 stmt_bind_primitive(mut stmt, data.right)
334 }
335 orm.Null {
336 stmt.bind_null()
337 }
338 []orm.Primitive {
339 stmt_bind_array(mut stmt, data)
340 }
341 []bool {
342 stmt_bind_array(mut stmt, data)
343 }
344 []f32 {
345 stmt_bind_array(mut stmt, data)
346 }
347 []f64 {
348 stmt_bind_array(mut stmt, data)
349 }
350 []i16 {
351 stmt_bind_array(mut stmt, data)
352 }
353 []i64 {
354 stmt_bind_array(mut stmt, data)
355 }
356 []i8 {
357 stmt_bind_array(mut stmt, data)
358 }
359 []int {
360 stmt_bind_array(mut stmt, data)
361 }
362 []string {
363 stmt_bind_array(mut stmt, data)
364 }
365 []time.Time {
366 stmt_bind_array(mut stmt, data)
367 }
368 []u16 {
369 stmt_bind_array(mut stmt, data)
370 }
371 []u32 {
372 stmt_bind_array(mut stmt, data)
373 }
374 []u64 {
375 stmt_bind_array(mut stmt, data)
376 }
377 []u8 {
378 stmt_bind_array(mut stmt, data)
379 }
380 []orm.InfixType {
381 stmt_bind_array(mut stmt, data)
382 }
383 }
384}
385
386// data_pointers_to_primitives returns an array of `Primitive`
387// cast from `data_pointers` using `types`.
388fn data_pointers_to_primitives(is_null []bool, data_pointers []&u8, types []int, field_types []FieldType) ![]orm.Primitive {
389 mut result := []orm.Primitive{}
390
391 for i, data in data_pointers {
392 mut primitive := orm.Primitive(0)
393 if !is_null[i] {
394 if field_types[i] in [.type_decimal, .type_newdecimal] {
395 decimal_value := unsafe { cstring_to_vstring(&char(data)) }
396 primitive = decimal_string_to_primitive(decimal_value, types[i])!
397 result << primitive
398 continue
399 }
400 match types[i] {
401 orm.type_idx['i8'] {
402 primitive = *(unsafe { &i8(data) })
403 }
404 orm.type_idx['i16'] {
405 primitive = *(unsafe { &i16(data) })
406 }
407 orm.type_idx['int'], orm.serial {
408 primitive = *(unsafe { &int(data) })
409 }
410 orm.type_idx['i64'] {
411 primitive = *(unsafe { &i64(data) })
412 }
413 orm.type_idx['u8'] {
414 primitive = *(unsafe { &u8(data) })
415 }
416 orm.type_idx['u16'] {
417 primitive = *(unsafe { &u16(data) })
418 }
419 orm.type_idx['u32'] {
420 primitive = *(unsafe { &u32(data) })
421 }
422 orm.type_idx['u64'] {
423 primitive = *(unsafe { &u64(data) })
424 }
425 orm.type_idx['f32'] {
426 primitive = *(unsafe { &f32(data) })
427 }
428 orm.type_idx['f64'] {
429 primitive = *(unsafe { &f64(data) })
430 }
431 orm.type_idx['bool'] {
432 primitive = *(unsafe { &bool(data) })
433 }
434 orm.type_string {
435 primitive = unsafe { cstring_to_vstring(&char(data)) }
436 }
437 orm.time_ {
438 match field_types[i] {
439 .type_long {
440 timestamp := *(unsafe { &int(data) })
441 primitive = time.unix(timestamp)
442 }
443 .type_datetime, .type_timestamp {
444 primitive = time.parse(unsafe { cstring_to_vstring(&char(data)) })!
445 }
446 else {}
447 }
448 }
449 orm.enum_ {
450 primitive = *(unsafe { &i64(data) })
451 }
452 else {
453 return error('Unknown type ${types[i]}')
454 }
455 }
456 } else {
457 primitive = orm.Null{}
458 }
459 result << primitive
460 }
461
462 return result
463}
464
465fn decimal_string_to_primitive(value string, typ int) !orm.Primitive {
466 return match typ {
467 orm.type_idx['i8'] {
468 orm.Primitive(strconv.atoi8(value)!)
469 }
470 orm.type_idx['i16'] {
471 orm.Primitive(strconv.atoi16(value)!)
472 }
473 orm.type_idx['int'], orm.serial {
474 orm.Primitive(strconv.atoi(value)!)
475 }
476 orm.type_idx['i64'], orm.enum_ {
477 orm.Primitive(strconv.atoi64(value)!)
478 }
479 orm.type_idx['u8'] {
480 orm.Primitive(strconv.atou8(value)!)
481 }
482 orm.type_idx['u16'] {
483 orm.Primitive(strconv.atou16(value)!)
484 }
485 orm.type_idx['u32'] {
486 orm.Primitive(strconv.atou32(value)!)
487 }
488 orm.type_idx['u64'] {
489 orm.Primitive(strconv.atou64(value)!)
490 }
491 orm.type_idx['f32'] {
492 orm.Primitive(f32(strconv.atof64(value)!))
493 }
494 orm.type_idx['f64'] {
495 orm.Primitive(strconv.atof64(value)!)
496 }
497 else {
498 return error('Unknown decimal target type ${typ}')
499 }
500 }
501}
502
503// mysql_type_from_v converts the V type to the corresponding MySQL type.
504fn mysql_type_from_v(typ int) !string {
505 sql_type := match typ {
506 orm.type_idx['i8'], orm.type_idx['u8'] {
507 'TINYINT'
508 }
509 orm.type_idx['i16'], orm.type_idx['u16'] {
510 'SMALLINT'
511 }
512 orm.type_idx['int'], orm.type_idx['u32'], orm.time_ {
513 'INT'
514 }
515 orm.type_idx['i64'], orm.type_idx['u64'], orm.enum_ {
516 'BIGINT'
517 }
518 orm.type_idx['f32'] {
519 'FLOAT'
520 }
521 orm.type_idx['f64'] {
522 'DOUBLE'
523 }
524 orm.type_string {
525 'TEXT'
526 }
527 orm.serial {
528 'SERIAL'
529 }
530 orm.type_idx['bool'] {
531 'BOOLEAN'
532 }
533 else {
534 return error('Unknown type ${typ}')
535 }
536 }
537
538 return sql_type
539}
540
541// convert_query_data_to_primitives converts the `data` representing the `QueryData`
542// into an array of `Primitive`.
543fn (db DB) convert_query_data_to_primitives(table string, data orm.QueryData) ![]orm.Primitive {
544 mut column_type_map := db.get_table_column_type_map(table)!
545 mut converted_data := []orm.Primitive{}
546 if data.fields.len == 0 {
547 return converted_data
548 }
549
550 for i, primitive in data.data {
551 field := data.fields[i % data.fields.len]
552 if primitive.type_name() == 'time.Time' {
553 if column_type_map[field] in ['datetime', 'timestamp'] {
554 converted_data << orm.Primitive((primitive as time.Time).str())
555 } else {
556 converted_data << primitive
557 }
558 } else {
559 converted_data << primitive
560 }
561 }
562
563 return converted_data
564}
565
566// get_table_column_type_map returns a map where the key represents the column name,
567// and the value represents its data type.
568fn (db DB) get_table_column_type_map(table string) !map[string]string {
569 data_type_query := "SELECT COLUMN_NAME, DATA_TYPE FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '${table}'"
570 mut column_type_map := map[string]string{}
571 results := db.query(data_type_query)!
572
573 for row in results.rows() {
574 column_type_map[row.vals[0]] = row.vals[1]
575 }
576
577 unsafe { results.free() }
578
579 return column_type_map
580}
581