From 71846299698a0f2cf02c92c5a1657835134b3b6f Mon Sep 17 00:00:00 2001 From: Louis Schmieder Date: Sun, 25 Apr 2021 17:57:55 +0200 Subject: [PATCH] orm: add initial pg support (#9827) --- .gitignore | 3 + doc/docs.md | 4 +- examples/database/orm.v | 28 ++- examples/database/{pg => psql}/.gitignore | 0 examples/database/{pg => psql}/customer.v | 4 +- examples/database/{pg => psql}/mydb.sql | 0 vlib/pg/pg.v | 4 +- vlib/v/gen/c/sql.v | 270 ++++++++++++++++++---- 8 files changed, 263 insertions(+), 50 deletions(-) rename examples/database/{pg => psql}/.gitignore (100%) rename examples/database/{pg => psql}/customer.v (96%) rename examples/database/{pg => psql}/mydb.sql (100%) diff --git a/.gitignore b/.gitignore index 4c39b0f7b9..0818b973fd 100644 --- a/.gitignore +++ b/.gitignore @@ -82,3 +82,6 @@ _docs cmd/tools/.disable_autorecompilation test.bin + +# ignore codespace env +.venv/ diff --git a/doc/docs.md b/doc/docs.md index 200913fa16..25f28b4fc6 100644 --- a/doc/docs.md +++ b/doc/docs.md @@ -3149,8 +3149,8 @@ fn test() []int { (This is still in an alpha state) -V has a built-in ORM (object-relational mapping) which supports SQLite and MySQL, -but soon it will support Postgres, MS SQL, and Oracle. +V has a built-in ORM (object-relational mapping) which supports SQLite, MySQL and Postgres, +but soon it will support MS SQL and Oracle. V's ORM provides a number of benefits: diff --git a/examples/database/orm.v b/examples/database/orm.v index 538bb49b19..286c1a70ce 100644 --- a/examples/database/orm.v +++ b/examples/database/orm.v @@ -1,5 +1,6 @@ import sqlite import mysql +import pg [table: 'modules'] struct Module { @@ -46,7 +47,8 @@ fn main() { eprintln(modul) - mysql() + // mysql() + psql() } fn mysql() { @@ -82,3 +84,27 @@ fn mysql() { } eprintln(m) } + +fn psql() { + mut db := pg.connect(host: 'localhost', user: 'test', password: 'abc', dbname: 'test') or { + panic(err) + } + + mod := Module{ + name: 'test' + nr_downloads: 10 + creator: User{ + age: 21 + name: 'VUser' + is_customer: true + } + } + + sql db { + create table Module + } + + sql db { + insert mod into Module + } +} diff --git a/examples/database/pg/.gitignore b/examples/database/psql/.gitignore similarity index 100% rename from examples/database/pg/.gitignore rename to examples/database/psql/.gitignore diff --git a/examples/database/pg/customer.v b/examples/database/psql/customer.v similarity index 96% rename from examples/database/pg/customer.v rename to examples/database/psql/customer.v index d159b83c6b..10dbe57834 100644 --- a/examples/database/pg/customer.v +++ b/examples/database/psql/customer.v @@ -12,7 +12,7 @@ struct Customer { } fn main() { - db := pg.connect(pg.Config{ + /*db := pg.connect(pg.Config{ host: 'localhost' //'127.0.0.1' user: 'postgres' dbname: 'customerdb' @@ -58,7 +58,7 @@ fn main() { name: 'John Doe' nr_orders: 10 } - db.insert(nc) + db.insert(nc)*/ } diff --git a/examples/database/pg/mydb.sql b/examples/database/psql/mydb.sql similarity index 100% rename from examples/database/pg/mydb.sql rename to examples/database/psql/mydb.sql diff --git a/vlib/pg/pg.v b/vlib/pg/pg.v index 615c781406..81d8ed4d32 100644 --- a/vlib/pg/pg.v +++ b/vlib/pg/pg.v @@ -11,6 +11,8 @@ import io // PostgreSQL Source Code // https://doxygen.postgresql.org/libpq-fe_8h.html #include +// for orm +#include pub struct DB { mut: @@ -59,7 +61,7 @@ fn C.PQexecParams(conn voidptr, command byteptr, nParams int, paramTypes int, pa fn C.PQputCopyData(conn voidptr, buffer byteptr, nbytes int) int -fn C.PQputCopyEnd(voidptr, int) int +fn C.PQputCopyEnd(voidptr, &byte) int fn C.PQgetCopyData(conn voidptr, buffer &byteptr, async int) int diff --git a/vlib/v/gen/c/sql.v b/vlib/v/gen/c/sql.v index a8929e1d7b..ca877a5a7d 100644 --- a/vlib/v/gen/c/sql.v +++ b/vlib/v/gen/c/sql.v @@ -40,6 +40,9 @@ fn (mut g Gen) sql_stmt(node ast.SqlStmt) { .mysql { g.mysql_stmt(node, typ) } + .psql { + g.psql_stmt(node, typ) + } else { verror('This database type `$typ` is not implemented yet in orm') // TODO add better error } @@ -55,6 +58,9 @@ fn (mut g Gen) sql_create_table(node ast.SqlStmt) { .mysql { g.mysql_create_table(node, typ) } + .psql { + g.psql_create_table(node, typ) + } else { verror('This database type `$typ` is not implemented yet in orm') // TODO add better error } @@ -70,6 +76,9 @@ fn (mut g Gen) sql_drop_table(node ast.SqlStmt) { .mysql { g.mysql_drop_table(node, typ) } + .psql { + g.psql_create_table(node, typ) + } else { verror('This database type `$typ` is not implemented yet in orm') // TODO add better error } @@ -92,7 +101,7 @@ fn (mut g Gen) sql_select_expr(node ast.SqlExpr, sub bool, line string) { } } -fn (mut g Gen) sql_bind(val string, len string, real_type ast.Type, typ SqlType) { +fn (mut g Gen) sql_bind(val string, len string, real_type ast.Type, typ SqlType, data []string) { match typ { .sqlite3 { g.sqlite3_bind(val, len, real_type) @@ -100,6 +109,9 @@ fn (mut g Gen) sql_bind(val string, len string, real_type ast.Type, typ SqlType) .mysql { g.mysql_bind(val, real_type) } + .psql { + g.psql_bind(val, data, real_type) + } else {} } } @@ -112,6 +124,9 @@ fn (mut g Gen) sql_type_from_v(typ SqlType, v_typ ast.Type) string { .mysql { return g.mysql_get_table_type(v_typ) } + .psql { + return g.psql_get_table_type(v_typ) + } else { // add error } @@ -141,7 +156,7 @@ fn (mut g Gen) sqlite3_stmt(node ast.SqlStmt, typ SqlType) { x := '${node.object_var_name}.$field.name' if field.typ == ast.string_type { g.writeln('sqlite3_bind_text($g.sql_stmt_name, ${i + 0}, (char*)${x}.str, ${x}.len, 0);') - } else if g.table.type_symbols[int(field.typ)].kind == .struct_ { + } else if g.table.get_type_symbol(field.typ).kind == .struct_ { // insert again expr := node.sub_structs[int(field.typ)] tmp_sql_stmt_name := g.sql_stmt_name @@ -271,7 +286,7 @@ fn (mut g Gen) sqlite3_select_expr(node ast.SqlExpr, sub bool, line string, sql_ g.writeln('if ($string_data != NULL) {') g.writeln('\t${tmp}.$field.name = tos_clone($string_data);') g.writeln('}') - } else if g.table.type_symbols[int(field.typ)].kind == .struct_ { + } else if g.table.get_type_symbol(field.typ).kind == .struct_ { id_name := g.new_tmp_var() g.writeln('//parse struct start') g.writeln('int $id_name = ${func}($g.sql_stmt_name, $i);') @@ -322,10 +337,10 @@ fn (mut g Gen) sqlite3_create_table(node ast.SqlStmt, typ SqlType) { fn (mut g Gen) sqlite3_drop_table(node ast.SqlStmt, typ SqlType) { table_name := g.get_table_name(node.table_expr) g.writeln('// sqlite3 table drop') - create_string := 'DROP TABLE $table_name;' + drop_string := 'DROP TABLE `$table_name`;' g.write('sqlite__DB_exec(') g.expr(node.db_expr) - g.writeln(', _SLIT("$create_string"));') + g.writeln(', _SLIT("$drop_string"));') } fn (mut g Gen) sqlite3_bind(val string, len string, typ ast.Type) { @@ -387,7 +402,7 @@ fn (mut g Gen) mysql_stmt(node ast.SqlStmt, typ SqlType) { } g.writeln('//$field.name ($field.typ)') x := '${node.object_var_name}.$field.name' - if g.table.type_symbols[int(field.typ)].kind == .struct_ { + if g.table.get_type_symbol(field.typ).kind == .struct_ { // insert again expr := node.sub_structs[int(field.typ)] tmp_sql_stmt_name := g.sql_stmt_name @@ -616,11 +631,11 @@ fn (mut g Gen) mysql_create_table(node ast.SqlStmt, typ SqlType) { fn (mut g Gen) mysql_drop_table(node ast.SqlStmt, typ SqlType) { table_name := g.get_table_name(node.table_expr) g.writeln('// mysql table drop') - create_string := 'DROP TABLE $table_name;' + drop_string := 'DROP TABLE `$table_name`;' tmp := g.new_tmp_var() g.write('Option_mysql__Result $tmp = mysql__Connection_query(&') g.expr(node.db_expr) - g.writeln(', _SLIT("$create_string"));') + g.writeln(', _SLIT("$drop_string"));') g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') } @@ -659,10 +674,10 @@ fn (mut g Gen) mysql_get_table_type(typ ast.Type) string { table_typ = 'BIGINT' } ast.f32_type { - table_typ = 'BIGINT' + table_typ = 'FLOAT' } ast.f64_type { - table_typ = 'BIGINT' + table_typ = 'DOUBLE' } ast.string_type { table_typ = 'TEXT' @@ -718,16 +733,162 @@ fn (mut g Gen) mysql_buffer_typ_from_field(field ast.StructField) (string, strin return buf_typ, sym } +// psql + +fn (mut g Gen) psql_stmt(node ast.SqlStmt, typ SqlType) { + g.sql_i = 0 + g.sql_idents = []string{} + param_values := g.new_tmp_var() + param_lens := g.new_tmp_var() + param_formats := g.new_tmp_var() + g.writeln('\n\t//psql insert') + db_name := g.new_tmp_var() + g.sql_stmt_name = g.new_tmp_var() + g.write('pg__DB $db_name = ') + g.expr(node.db_expr) + g.writeln(';') + stmt_name := g.new_tmp_var() + g.write('string $stmt_name = _SLIT("') + g.sql_defaults(node, typ, param_values, param_lens, param_formats) + g.writeln(';') + + g.writeln('char *$param_values[$g.sql_i]; //param values') + g.writeln('int $param_lens[$g.sql_i]; //param lens') + g.writeln('int $param_formats[$g.sql_i]; // param formats') + + if node.kind == .insert { + for i, field in node.fields { + if g.get_sql_field_type(field) == ast.Type(-1) { + continue + } + g.writeln('//$field.name ($field.typ)') + x := '${node.object_var_name}.$field.name' + field_type := g.get_sql_field_type(field) + if g.table.get_type_symbol(field.typ).kind == .struct_ { + // insert again + expr := node.sub_structs[int(field.typ)] + tmp_sql_stmt_name := g.sql_stmt_name + tmp_sql_table_name := g.sql_table_name + g.sql_stmt(expr) + g.sql_stmt_name = tmp_sql_stmt_name + g.sql_table_name = tmp_sql_table_name + + res := g.new_tmp_var() + g.write('Option_int $res = pg__DB_q_int($db_name, _SLIT("SELECT LASTVAL();"));') + g.writeln('if (${res}.state != 0) { IError err = ${res}.err; eprintln(_STR("\\000%.*s", 2, IError_str(err))); }') + g.sql_buf = strings.new_builder(100) + g.sql_bind('${res}.data', '', ast.int_type, typ, [ + (i - 1).str(), + param_values, + param_lens, + param_formats, + ]) + g.writeln(g.sql_buf.str()) + } else { + g.sql_buf = strings.new_builder(100) + g.sql_bind(x, '', field_type, typ, [(i - 1).str(), param_values, param_lens, + param_formats, + ]) + g.writeln(g.sql_buf.str()) + } + } + } + binds := g.sql_buf.str() + g.sql_buf = strings.new_builder(100) + g.writeln(binds) + + res := g.new_tmp_var() + g.writeln('PGresult * $res = PQexecParams(${db_name}.conn, ${stmt_name}.str, $g.sql_i, 0, $param_values, $param_lens, $param_formats, 0);') + g.writeln('Option_Array_pg__Row ${res}_rows = pg__DB_handle_error_or_result($db_name, $res, _SLIT("$stmt_name"));') + g.writeln('if (${res}_rows.state != 0) { IError err = ${res}_rows.err; eprintln(_STR("\\000%.*s", 2, IError_str(err))); }') +} + +fn (mut g Gen) psql_create_table(node ast.SqlStmt, typ SqlType) { + g.writeln('// psql table creator') + create_string := g.table_gen(node, typ) + tmp := g.new_tmp_var() + g.write('Option_Array_pg__Row $tmp = pg__DB_exec(') + g.expr(node.db_expr) + g.writeln(', _SLIT("$create_string"));') + g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') +} + +fn (mut g Gen) psql_drop_table(node ast.SqlStmt, typ SqlType) { + table_name := g.get_table_name(node.table_expr) + g.writeln('// psql table drop') + drop_string := 'DROP TABLE "$table_name";' + tmp := g.new_tmp_var() + g.write('Option_Array_pg__Row $tmp = pg__DB_exec(&') + g.expr(node.db_expr) + g.writeln(', _SLIT("$drop_string"));') + g.writeln('if (${tmp}.state != 0) { IError err = ${tmp}.err; eprintln(_STR("Something went wrong\\000%.*s", 2, IError_str(err))); }') +} + +fn (mut g Gen) psql_get_table_type(typ ast.Type) string { + mut table_typ := '' + match typ { + ast.i8_type, ast.byte_type, ast.bool_type { + table_typ = 'CHAR(1)' + } + ast.i16_type, ast.u16_type { + table_typ = 'SMALLINT' + } + ast.int_type, ast.u32_type { + table_typ = 'INT' + } + ast.i64_type, ast.u64_type { + table_typ = 'BIGINT' + } + ast.f32_type { + table_typ = 'FLOAT4' + } + ast.f64_type { + table_typ = 'FLOAT8' + } + ast.string_type { + table_typ = 'TEXT' + } + -1 { + table_typ = 'SERIAL' + } + else {} + } + return table_typ +} + +fn (mut g Gen) psql_bind(val string, data []string, typ ast.Type) { + tmp := g.new_tmp_var() + g.sql_idents << tmp + g.sql_buf.write_string('char* $tmp = ') + if typ.is_number() { + g.sql_buf.writeln('(char *) htonl($val);') + g.sql_buf.writeln('\t${data[1]}[${data[0]}] = &$tmp;') + g.sql_buf.writeln('\t${data[2]}[${data[0]}] = sizeof($tmp);') + g.sql_buf.writeln('\t${data[3]}[${data[0]}] = 1;') + } else if typ.is_string() { + g.sql_buf.writeln('(char *) ${val}.str;') + g.sql_buf.writeln('\t${data[1]}[${data[0]}] = &$tmp;') + g.sql_buf.writeln('\t${data[2]}[${data[0]}] = 0;') + g.sql_buf.writeln('\t${data[3]}[${data[0]}] = 0;') + } else { + g.sql_buf.writeln('(char *) htonl(0);') + g.sql_buf.writeln('if ($val) { $tmp = (char *) htonl(1); }') + g.sql_buf.writeln('\t${data[1]}[${data[0]}] = &$tmp;') + g.sql_buf.writeln('\t${data[2]}[${data[0]}] = sizeof($tmp);') + g.sql_buf.writeln('\t${data[3]}[${data[0]}] = 1;') + } +} + // utils fn (mut g Gen) sql_expr_defaults(node ast.SqlExpr, sql_typ SqlType) { if node.has_where && node.where_expr is ast.InfixExpr { - g.expr_to_sql(node.where_expr, sql_typ) + g.expr_to_sql(node.where_expr, sql_typ, []) } if node.has_order { g.write(' ORDER BY ') g.sql_side = .left - g.expr_to_sql(node.order_expr, sql_typ) + g.expr_to_sql(node.order_expr, sql_typ, []) if node.has_desc { g.write(' DESC ') } @@ -737,12 +898,12 @@ fn (mut g Gen) sql_expr_defaults(node ast.SqlExpr, sql_typ SqlType) { if node.has_limit { g.write(' LIMIT ') g.sql_side = .right - g.expr_to_sql(node.limit_expr, sql_typ) + g.expr_to_sql(node.limit_expr, sql_typ, []) } if node.has_offset { g.write(' OFFSET ') g.sql_side = .right - g.expr_to_sql(node.offset_expr, sql_typ) + g.expr_to_sql(node.offset_expr, sql_typ, []) } } @@ -768,21 +929,25 @@ fn (mut g Gen) get_base_sql_select_query(node ast.SqlExpr) string { return sql_query } -fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType) { +fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType, psql_data ...string) { table_name := g.get_table_name(node.table_expr) + mut lit := '`' + if typ == .psql { + lit = '\\"' + } if node.kind == .insert { - g.write('INSERT INTO `$table_name` (') + g.write('INSERT INTO $lit$table_name$lit (') } else if node.kind == .update { - g.write('UPDATE `$table_name` SET ') + g.write('UPDATE $lit$table_name$lit SET ') } else if node.kind == .delete { - g.write('DELETE FROM `$table_name` ') + g.write('DELETE FROM $lit$table_name$lit ') } if node.kind == .insert { for i, field in node.fields { if g.get_sql_field_type(field) == ast.Type(-1) { continue } - g.write('`${g.get_field_name(field)}`') + g.write('$lit${g.get_field_name(field)}$lit') if i < node.fields.len - 1 { g.write(', ') } @@ -792,21 +957,16 @@ fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType) { if g.get_sql_field_type(field) == ast.Type(-1) { continue } - if typ == .sqlite3 { - g.write('?${i + 0}') - } else if typ == .mysql { - g.write('?') - } + g.inc_sql_i(typ) if i < node.fields.len - 1 { g.write(', ') } - g.sql_i++ } g.write(')') } else if node.kind == .update { for i, col in node.updated_columns { g.write(' ${g.get_field_name(g.get_struct_field(col))} = ') - g.expr_to_sql(node.update_exprs[i], typ) + g.expr_to_sql(node.update_exprs[i], typ, psql_data) if i < node.updated_columns.len - 1 { g.write(', ') } @@ -816,16 +976,20 @@ fn (mut g Gen) sql_defaults(node ast.SqlStmt, typ SqlType) { g.write(' WHERE ') } if node.kind == .update || node.kind == .delete { - g.expr_to_sql(node.where_expr, typ) + g.expr_to_sql(node.where_expr, typ, psql_data) } - g.write('")') + g.write(';")') } fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string { typ_sym := g.table.get_type_symbol(node.table_expr.typ) struct_data := typ_sym.struct_info() table_name := g.get_table_name(node.table_expr) - mut create_string := 'CREATE TABLE IF NOT EXISTS `$table_name` (' + mut lit := '`' + if typ == .psql { + lit = '\\"' + } + mut create_string := 'CREATE TABLE IF NOT EXISTS $lit$table_name$lit (' mut fields := []string{} @@ -882,7 +1046,7 @@ fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string { continue } } - stmt = '`$name` $converted_typ' + stmt = '$lit$name$lit $converted_typ' if field.has_default_expr && typ != .mysql { stmt += ' DEFAULT ' @@ -903,20 +1067,20 @@ fn (mut g Gen) table_gen(node ast.SqlStmt, typ SqlType) string { for k, v in unique { mut tmp := []string{} for f in v { - tmp << '`$f`' + tmp << '$lit$f$lit' } fields << '/* $k */UNIQUE(${tmp.join(', ')})' } } if typ == .mysql { - fields << 'PRIMARY KEY(`$primary`)' + fields << 'PRIMARY KEY($lit$primary$lit)' } create_string += fields.join(', ') create_string += ');' return create_string } -fn (mut g Gen) expr_to_sql(expr ast.Expr, typ SqlType) { +fn (mut g Gen) expr_to_sql(expr ast.Expr, typ SqlType, psql_data []string) { // Custom handling for infix exprs (since we need e.g. `and` instead of `&&` in SQL queries), // strings. Everything else (like numbers, a.b) is handled by g.expr() // @@ -925,7 +1089,7 @@ fn (mut g Gen) expr_to_sql(expr ast.Expr, typ SqlType) { match expr { ast.InfixExpr { g.sql_side = .left - g.expr_to_sql(expr.left, typ) + g.expr_to_sql(expr.left, typ, psql_data) match expr.op { .ne { g.write(' != ') } .eq { g.write(' = ') } @@ -942,24 +1106,26 @@ fn (mut g Gen) expr_to_sql(expr ast.Expr, typ SqlType) { else {} } g.sql_side = .right - g.expr_to_sql(expr.right, typ) + g.expr_to_sql(expr.right, typ, psql_data) } ast.StringLiteral { // g.write("'$it.val'") g.inc_sql_i(typ) g.sql_bind('"$expr.val"', expr.val.len.str(), g.sql_get_real_type(ast.string_type), - typ) + typ, g.append_arr(g.sql_i, psql_data)) } ast.IntegerLiteral { g.inc_sql_i(typ) - g.sql_bind(expr.val, '', g.sql_get_real_type(ast.int_type), typ) + g.sql_bind(expr.val, '', g.sql_get_real_type(ast.int_type), typ, g.append_arr(g.sql_i, + psql_data)) } ast.BoolLiteral { // true/false literals were added to Sqlite 3.23 (2018-04-02) // but lots of apps/distros use older sqlite (e.g. Ubuntu 18.04 LTS ) g.inc_sql_i(typ) eval := if expr.val { '1' } else { '0' } - g.sql_bind(eval, '', g.sql_get_real_type(ast.byte_type), typ) + g.sql_bind(eval, '', g.sql_get_real_type(ast.byte_type), typ, g.append_arr(g.sql_i, + psql_data)) } ast.Ident { // `name == user_name` => `name == ?1` @@ -975,12 +1141,14 @@ fn (mut g Gen) expr_to_sql(expr ast.Expr, typ SqlType) { if typ == .sqlite3 { if ityp == ast.string_type { g.sql_bind('${expr.name}.str', '${expr.name}.len', g.sql_get_real_type(ityp), - typ) + typ, g.append_arr(g.sql_i, psql_data)) } else { - g.sql_bind(expr.name, '', g.sql_get_real_type(ityp), typ) + g.sql_bind(expr.name, '', g.sql_get_real_type(ityp), typ, g.append_arr(g.sql_i, + psql_data)) } } else { - g.sql_bind('%$g.sql_i.str()', '', g.sql_get_real_type(ityp), typ) + g.sql_bind('%$g.sql_i.str()', '', g.sql_get_real_type(ityp), typ, + g.append_arr(g.sql_i, psql_data)) g.sql_idents << expr.name g.sql_idents_types << g.sql_get_real_type(ityp) } @@ -993,7 +1161,7 @@ fn (mut g Gen) expr_to_sql(expr ast.Expr, typ SqlType) { } ident := expr.expr as ast.Ident g.sql_bind(ident.name + '.' + expr.field_name, '', g.sql_get_real_type(expr.typ), - typ) + typ, g.append_arr(g.sql_i, psql_data)) } else { g.expr(expr) @@ -1007,6 +1175,13 @@ fn (mut g Gen) expr_to_sql(expr ast.Expr, typ SqlType) { */ } +fn (mut g Gen) append_arr(i int, arr []string) []string { + mut tmp := []string{} + tmp << i.str() + tmp << arr + return tmp +} + fn (mut g Gen) get_struct_field_typ(f string) ast.Type { sym := g.table.get_type_symbol(g.table.type_idxs[g.sql_table_name]) @@ -1035,8 +1210,12 @@ fn (mut g Gen) sql_get_real_type(typ ast.Type) ast.Type { fn (mut g Gen) inc_sql_i(typ SqlType) { g.sql_i++ - if typ == .sqlite3 { - g.write('?') + if typ == .psql { + g.write('$') + } else { + g.write('?') // used in sqlite `?i` and mysql `?` + } + if typ != .mysql { g.write('$g.sql_i') } } @@ -1066,6 +1245,9 @@ fn (mut g Gen) parse_db_from_type_string(name string) SqlType { 'mysql.Connection' { return .mysql } + 'pg.DB' { + return .psql + } else { return .unknown }