1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00
v/vlib/compiler/query.v

341 lines
8.4 KiB
V
Raw Normal View History

2019-08-11 00:02:48 +03:00
// Copyright (c) 2019 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module compiler
2019-08-09 19:10:59 +03:00
import strings
2019-08-09 19:10:59 +03:00
fn sql_params2params_gen(sql_params []string, sql_types []string, qprefix string) string {
mut params_gen := ''
for i, mparam in sql_params {
param := mparam.trim_space()
2019-12-20 00:29:37 +03:00
paramtype := sql_types[i]
if param[0].is_digit() {
params_gen += '${qprefix}params[$i] = int_str($param).str;\n'
2019-12-20 00:29:37 +03:00
}
else if param[0] == `\'` {
sparam := param.trim("\'")
params_gen += '${qprefix}params[$i] = "$sparam";\n'
2019-12-20 00:29:37 +03:00
}
else {
// A variable like q.nr_orders
if paramtype == 'int' {
params_gen += '${qprefix}params[$i] = int_str( $param ).str;\n'
2019-12-20 00:29:37 +03:00
}
else if paramtype == 'string' {
params_gen += '${qprefix}params[$i] = ${param}.str;\n'
2019-12-20 00:29:37 +03:00
}
else {
verror('orm: only int and string variable types are supported in queries')
}
}
}
2019-12-20 00:29:37 +03:00
// println('>>>>>>>> params_gen')
// println( params_gen )
return params_gen
}
// `db.select from User where id == 1 && nr_bookings > 0`
2019-08-09 19:10:59 +03:00
fn (p mut Parser) select_query(fn_ph int) string {
// NB: qprefix and { p.sql_i, p.sql_params, p.sql_types } SHOULD be reset for each query,
// because we can have many queries in the _same_ scope.
2019-12-20 00:29:37 +03:00
qprefix := p.get_tmp().replace('tmp', 'sql') + '_'
p.sql_i = 0
2019-11-14 09:18:07 +03:00
p.sql_params = []
2019-12-20 00:29:37 +03:00
if false {
}
2019-11-14 09:18:07 +03:00
p.sql_types = []
mut q := 'select '
p.check(.key_select)
p.fspace()
n := p.check_name()
p.fspace()
2019-08-09 19:10:59 +03:00
if n == 'count' {
q += 'count(*) from '
p.check_name()
p.fspace()
}
table_name := p.check_name()
p.fspace()
2019-12-09 17:10:44 +03:00
// Register this type's fields as variables so they can be used in `where`
// expressions
typ := p.table.find_type(table_name)
2019-08-09 19:10:59 +03:00
if typ.name == '' {
p.error('unknown type `$table_name`')
}
2019-12-20 00:29:37 +03:00
// fields := typ.fields.filter(typ == 'string' || typ == 'int')
// get only string and int fields
mut fields := []Var
for i, field in typ.fields {
2019-12-09 17:10:44 +03:00
if !(field.typ in ['string', 'int', 'bool']) {
println('orm: skipping $field.name')
continue
}
2019-12-14 02:02:14 +03:00
if field.attr.contains('skip') {
continue
}
fields << field
}
if fields.len == 0 {
p.error('V orm: select: empty fields in `$table_name`')
}
if fields[0].name != 'id' {
p.error('V orm: `id int` must be the first field in `$table_name`')
}
// 'select id, name, age from...'
2019-08-09 19:10:59 +03:00
if n == 'from' {
for i, field in fields {
q += field.name
if i < fields.len - 1 {
q += ', '
}
}
q += ' from '
}
for field in fields {
2019-12-20 00:29:37 +03:00
// println('registering sql field var $field.name')
2019-12-09 17:10:44 +03:00
if !(field.typ in ['string', 'int', 'bool']) {
println('orm: skipping $field.name')
continue
}
2019-12-20 00:29:37 +03:00
p.register_var({
field |
is_mut:true,
is_used:true,
is_changed:true
})
}
2019-12-09 17:10:44 +03:00
q += table_name + 's'
// `where` statement
if p.tok == .name && p.lit == 'where' {
p.next()
p.fspace()
p.is_sql = true
2019-12-20 00:29:37 +03:00
_,expr := p.tmp_expr()
p.is_sql = false
2019-11-11 05:22:34 +03:00
q += ' where ' + expr
}
// limit?
mut query_one := false
if p.tok == .name && p.lit == 'limit' {
p.fspace()
p.next()
p.fspace()
p.is_sql = true
2019-12-20 00:29:37 +03:00
_,limit := p.tmp_expr()
p.fspace()
p.is_sql = false
q += ' limit ' + limit
// `limit 1` means we are getting `?User`, not `[]User`
if limit.trim_space() == '1' {
query_one = true
}
}
println('sql query="$q"')
p.cgen.insert_before('// DEBUG_SQL prefix: $qprefix | fn_ph: $fn_ph | query: "$q" ')
2019-08-09 19:10:59 +03:00
if n == 'count' {
p.cgen.set_placeholder(fn_ph, 'pg__DB_q_int(')
p.gen(', tos2("$q"))')
2019-12-20 00:29:37 +03:00
}
else {
// Build an object, assign each field.
tmp := p.get_tmp()
mut obj_gen := strings.new_builder(300)
for i, field in fields {
mut cast := ''
2019-08-09 19:10:59 +03:00
if field.typ == 'int' {
cast = 'v_string_int'
}
2019-12-09 17:10:44 +03:00
else if field.typ == 'bool' {
cast = 'string_bool'
}
2019-12-20 00:29:37 +03:00
obj_gen.writeln('${qprefix}${tmp}.$field.name = ' + '${cast}(*(string*)array_get(${qprefix}row.vals, $i));')
}
// One object
if query_one {
2019-12-20 00:29:37 +03:00
mut params_gen := sql_params2params_gen(p.sql_params, p.sql_types, qprefix)
2019-08-09 19:10:59 +03:00
p.cgen.insert_before('
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) );
}
2019-08-09 19:10:59 +03:00
')
p.cgen.resetln('opt_${qprefix}$tmp')
}
// Array
2019-08-09 19:10:59 +03:00
else {
q += ' order by id'
2019-12-20 00:29:37 +03:00
params_gen := sql_params2params_gen(p.sql_params, p.sql_types, qprefix)
p.cgen.insert_before('char* ${qprefix}params[$p.sql_i];
$params_gen
2019-08-13 14:50:19 +03:00
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);
2019-08-09 19:10:59 +03:00
// TODO preallocate
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);
}
2019-08-09 19:10:59 +03:00
')
p.cgen.resetln('${qprefix}arr_$tmp')
2019-12-20 00:29:37 +03:00
}
}
2019-08-09 19:10:59 +03:00
if n == 'count' {
return 'int'
2019-12-20 00:29:37 +03:00
}
else if query_one {
2019-12-05 22:31:56 +03:00
opt_type := 'Option_$table_name'
p.cgen.typedefs << 'typedef Option $opt_type;'
2019-12-20 00:29:37 +03:00
p.table.register_builtin(opt_type)
return opt_type
2019-12-20 00:29:37 +03:00
}
else {
p.register_array('array_$table_name')
return 'array_$table_name'
}
}
2019-08-09 19:10:59 +03:00
// `db.insert(user)`
fn (p mut Parser) insert_query(fn_ph int) {
p.check_name()
p.check(.lpar)
var_name := p.check_name()
p.check(.rpar)
2019-12-20 00:29:37 +03:00
var := p.find_var(var_name) or {
return
}
typ := p.table.find_type(var.typ)
mut fields := []Var
for i, field in typ.fields {
if field.typ != 'string' && field.typ != 'int' {
continue
}
fields << field
}
if fields.len == 0 {
p.error('V orm: insert: empty fields in `$var.typ`')
}
if fields[0].name != 'id' {
p.error('V orm: `id int` must be the first field in `$var.typ`')
}
table_name := var.typ
2019-12-20 00:29:37 +03:00
mut sfields := '' // 'name, city, country'
mut params := '' // params[0] = 'bob'; params[1] = 'Vienna';
2019-12-20 00:29:37 +03:00
mut vals := '' // $1, $2, $3...
mut nr_vals := 0
for i, field in fields {
2019-08-09 19:10:59 +03:00
if field.name == 'id' {
continue
}
sfields += field.name
vals += '$' + i.str()
nr_vals++
2019-08-09 19:10:59 +03:00
params += 'params[${i-1}] = '
if field.typ == 'string' {
params += '$var_name . $field.name .str;\n'
2019-12-20 00:29:37 +03:00
}
else if field.typ == 'int' {
params += 'int_str($var_name . $field.name).str;\n'
2019-12-20 00:29:37 +03:00
}
else {
p.error('V ORM: unsupported type `$field.typ`')
}
if i < fields.len - 1 {
sfields += ', '
vals += ', '
}
}
p.cgen.insert_before('char* params[$nr_vals];' + params)
2019-08-09 19:10:59 +03:00
p.cgen.set_placeholder(fn_ph, 'PQexecParams( ')
p.genln('.conn, "insert into $table_name ($sfields) values ($vals)", $nr_vals,
0, params, 0, 0, 0)')
}
2019-08-09 19:10:59 +03:00
2019-12-10 14:32:12 +03:00
// `db.update User set nr_orders=nr_orders+1`
fn (p mut Parser) update_query(fn_ph int) {
println('update query')
p.check_name()
p.fspace()
2019-12-10 14:32:12 +03:00
table_name := p.check_name()
p.fspace()
2019-12-10 14:32:12 +03:00
typ := p.table.find_type(table_name)
if typ.name == '' {
p.error('unknown type `$table_name`')
}
set := p.check_name()
p.fspace()
2019-12-10 14:32:12 +03:00
if set != 'set' {
p.error('expected `set`')
}
if typ.fields.len == 0 {
p.error('V orm: update: empty fields in `$typ.name`')
}
if typ.fields[0].name != 'id' {
p.error('V orm: `id int` must be the first field in `$typ.name`')
}
field := p.check_name()
p.fspace()
2019-12-10 14:32:12 +03:00
p.check(.assign)
p.fspace()
2019-12-10 14:32:12 +03:00
for f in typ.fields {
if !(f.typ in ['string', 'int', 'bool']) {
println('orm: skipping $f.name')
continue
}
2019-12-20 00:29:37 +03:00
p.register_var({
f |
is_mut:true,
is_used:true,
is_changed:true
})
2019-12-10 14:32:12 +03:00
}
mut q := 'update ${typ.name}s set $field='
p.is_sql = true
2019-12-20 00:29:37 +03:00
set_typ,expr := p.tmp_expr()
2019-12-10 14:32:12 +03:00
p.is_sql = false
// TODO this hack should not be necessary
if set_typ == 'bool' {
if expr.trim_space() == '1' {
q += 'true'
}
else {
q += 'false'
}
2019-12-20 00:29:37 +03:00
}
else {
2019-12-10 14:32:12 +03:00
q += expr
}
// where
if p.tok == .name && p.lit == 'where' {
p.next()
p.fspace()
2019-12-10 14:32:12 +03:00
p.is_sql = true
2019-12-20 00:29:37 +03:00
_,wexpr := p.tmp_expr()
2019-12-10 14:32:12 +03:00
p.is_sql = false
q += ' where ' + wexpr
}
nr_vals := 0
2019-12-20 00:29:37 +03:00
p.cgen.insert_before('char* params[$nr_vals];') // + params)
2019-12-10 14:32:12 +03:00
p.cgen.set_placeholder(fn_ph, 'PQexecParams( ')
println('update q="$q"')
p.genln('.conn, "$q", $nr_vals, 0, params, 0, 0, 0)')
}