From 49899c649c27d85396f94bc50002af92bc251b80 Mon Sep 17 00:00:00 2001 From: Delyan Angelov Date: Tue, 20 Aug 2019 15:34:34 +0300 Subject: [PATCH] orm: fix select .. limit 1 . This case now returns an ?Row. --- compiler/query.v | 76 ++++++++++++++++++++++++++++++---------------- vlib/builtin/int.v | 2 +- vlib/pg/pg.v | 22 ++++++++------ 3 files changed, 63 insertions(+), 37 deletions(-) diff --git a/compiler/query.v b/compiler/query.v index 4be03cf22b..401df76d39 100644 --- a/compiler/query.v +++ b/compiler/query.v @@ -8,6 +8,12 @@ import strings // `db.select from User where id == 1 && nr_bookings > 0` fn (p mut Parser) select_query(fn_ph int) string { + // NB: qprefix, p.sql_i, p.sql_params SHOULD be reset for each query, + // because we can have many queries in the _same_ scope. + qprefix := p.get_tmp().replace('tmp','sql') + '_' + p.sql_i = 0 + p.sql_params = '' + mut q := 'select ' p.check(.key_select) n := p.check_name() @@ -73,69 +79,87 @@ fn (p mut Parser) select_query(fn_ph int) string { p.is_sql = false limit := p.cgen.end_tmp() q += ' limit ' + limit - // `limit 1` means we are getting `User`, not `[]User` + // `limit 1` means we are getting `?User`, not `[]User` if limit.trim_space() == '1' { query_one = true } } - println('sql query="$q"') + println('sql query="$q"') + p.cgen.insert_before('// DEBUG_SQL prefix: $qprefix | fn_ph: $fn_ph | query: "$q" ') + if n == 'count' { p.cgen.set_placeholder(fn_ph, 'pg__DB_q_int(') p.gen(', tos2("$q"))') } else { // Build an object, assign each field. tmp := p.get_tmp() - mut obj_gen := strings.new_builder(100) + mut obj_gen := strings.new_builder(300) for i, field in fields { mut cast := '' if field.typ == 'int' { cast = 'string_int' } - obj_gen.writeln('$tmp . $field.name = $cast( *(string*)array__get(row.vals, $i) );') + obj_gen.writeln('${qprefix}$tmp . $field.name = $cast( *(string*)array__get(${qprefix}row.vals, $i) );') } // One object if query_one { + mut params_gen := '' + params := p.sql_params.split(',') + for i, param in params { + params_gen += '${qprefix}params[$i] = int_str($param).str;' + } p.cgen.insert_before(' -pg__Row row = pg__DB_exec_one(db, tos2("$q")); -$table_name $tmp; -${obj_gen.str()} +char* ${qprefix}params[$p.sql_i]; +$params_gen + +Option_${table_name} opt_${qprefix}$tmp; +void* ${qprefix}res = PQexecParams(db.conn, "$q", $p.sql_i, 0, ${qprefix}params, 0, 0, 0) ; +array_pg__Row ${qprefix}rows = pg__res_to_rows ( ${qprefix}res ) ; +Option_pg__Row opt_${qprefix}row = pg__rows_first_or_empty( ${qprefix}rows ); +if (! opt_${qprefix}row . ok ) { + opt_${qprefix}$tmp = v_error( opt_${qprefix}row . error ); +}else{ + $table_name ${qprefix}$tmp; + pg__Row ${qprefix}row = *(pg__Row*) opt_${qprefix}row . data; +${obj_gen.str()} + opt_${qprefix}$tmp = opt_ok( & ${qprefix}$tmp, sizeof($table_name) ); +} ') - p.cgen.resetln(tmp) + p.cgen.resetln('opt_${qprefix}$tmp') } // Array else { - q += ' order by id' - mut params_gen := '' - params := p.sql_params.split(',') - for i, param in params { - params_gen += 'params[$i] = int_str($param).str;' - } - - p.cgen.insert_before('char* params[$p.sql_i]; + q += ' order by id' + mut params_gen := '' + params := p.sql_params.split(',') + for i, param in params { + params_gen += '${qprefix}params[$i] = int_str($param).str;' + } + p.cgen.insert_before('char* ${qprefix}params[$p.sql_i]; $params_gen -void* res = PQexecParams(db.conn, "$q", $p.sql_i, 0, params, 0, 0, 0) ; -array_pg__Row rows = pg__res_to_rows(res); +void* ${qprefix}res = PQexecParams(db.conn, "$q", $p.sql_i, 0, ${qprefix}params, 0, 0, 0) ; +array_pg__Row ${qprefix}rows = pg__res_to_rows(${qprefix}res); // TODO preallocate -array arr_$tmp = new_array(0, 0, sizeof($table_name)); -for (int i = 0; i < rows.len; i++) { - pg__Row row = *(pg__Row*)array__get(rows, i); - $table_name $tmp; - ${obj_gen.str()} - _PUSH(&arr_$tmp, $tmp, ${tmp}2, $table_name); +array ${qprefix}arr_$tmp = new_array(0, 0, sizeof($table_name)); +for (int i = 0; i < ${qprefix}rows.len; i++) { + pg__Row ${qprefix}row = *(pg__Row*)array__get(${qprefix}rows, i); + $table_name ${qprefix}$tmp; + ${obj_gen.str()} + _PUSH(&${qprefix}arr_$tmp, ${qprefix}$tmp, ${tmp}2, $table_name); } ') - p.cgen.resetln('arr_$tmp') + p.cgen.resetln('${qprefix}arr_$tmp') } } if n == 'count' { return 'int' } else if query_one { - return table_name + return 'Option_$table_name' } else { p.register_array('array_$table_name') return 'array_$table_name' diff --git a/vlib/builtin/int.v b/vlib/builtin/int.v index 997025f0be..4f28cffdd9 100644 --- a/vlib/builtin/int.v +++ b/vlib/builtin/int.v @@ -48,7 +48,7 @@ pub fn (nn int) str() string { return '0' } max := 16 - mut buf := malloc(max) + mut buf := calloc(max) mut len := 0 mut is_neg := false if n < 0 { diff --git a/vlib/pg/pg.v b/vlib/pg/pg.v index 954cc35321..d278e2e2c6 100644 --- a/vlib/pg/pg.v +++ b/vlib/pg/pg.v @@ -29,6 +29,7 @@ pub: host string user string password string + dbname string } fn C.PQconnectdb(a byteptr) *C.PGconn @@ -106,22 +107,23 @@ pub fn (db DB) exec(query string) []pg.Row { return res_to_rows(res) } -pub fn (db DB) exec_one(query string) pg.Row { +fn rows_first_or_empty(rows []pg.Row) pg.Row? { + if rows.len == 0 { + return error('no row') + } + return rows[0] +} + +pub fn (db DB) exec_one(query string) pg.Row? { res := C.PQexec(db.conn, query.str) e := string(C.PQerrorMessage(db.conn)) if e != '' { - println('pg exec error:') - println(e) - return Row{} + return error('pg exec error: "$e"') } - rows := res_to_rows(res) - if rows.len == 0 { - return Row{} - } - return rows[0] + row := rows_first_or_empty( res_to_rows(res) ) + return row } - // pub fn (db DB) exec_param2(query string, param, param2 string) []pg.Row { mut param_vals := [2]byteptr