module pg #flag -lpq #flag linux -I/usr/include/postgresql #flag darwin -I/opt/local/include/postgresql11 #flag windows -I @VROOT/thirdparty/pg/include #flag windows -L @VROOT/thirdparty/pg/win64 #include pub struct DB { mut: conn &C.PGconn } pub struct Row { pub mut: vals []string } struct C.PGResult { } pub struct Config { pub: host string port int = 5432 user string password string dbname string } fn C.PQconnectdb(a byteptr) &C.PGconn fn C.PQerrorMessage(voidptr) byteptr fn C.PQgetvalue(voidptr, int, int) byteptr fn C.PQstatus(voidptr) int fn C.PQntuples(voidptr) int fn C.PQnfields(voidptr) int fn C.PQexec(voidptr) voidptr fn C.PQexecParams(voidptr) voidptr fn C.PQclear(voidptr) voidptr pub fn connect(config Config) ?DB { conninfo := 'host=$config.host port=$config.port user=$config.user dbname=$config.dbname password=$config.password' conn := C.PQconnectdb(conninfo.str) status := C.PQstatus(conn) println("status=$status") if status != C.CONNECTION_OK { error_msg := C.PQerrorMessage(conn) return error ('Connection to a PG database failed: ' + unsafe { error_msg.vstring() } ) } return DB {conn: conn} } fn res_to_rows(res voidptr) []Row { nr_rows := C.PQntuples(res) nr_cols := C.PQnfields(res) mut rows := []Row{} for i in 0..nr_rows { mut row := Row{} for j in 0..nr_cols { val := C.PQgetvalue(res, i, j) sval := unsafe { val.vstring() } row.vals << sval } rows << row } C.PQclear(res) return rows } pub fn (db DB) q_int(query string) int { rows := db.exec(query) if rows.len == 0 { println('q_int "$query" not found') return 0 } row := rows[0] if row.vals.len == 0 { return 0 } val := row.vals[0] return val.int() } pub fn (db DB) q_string(query string) string { rows := db.exec(query) if rows.len == 0 { println('q_string "$query" not found') return '' } row := rows[0] if row.vals.len == 0 { return '' } val := row.vals[0] return val } pub fn (db DB) q_strings(query string) []Row { return db.exec(query) } pub fn (db DB) exec(query string) []Row { res := C.PQexec(db.conn, query.str) e := unsafe { C.PQerrorMessage(db.conn).vstring() } if e != '' { println('pg exec error:') println(e) return res_to_rows(res) } return res_to_rows(res) } fn rows_first_or_empty(rows []Row) ?Row { if rows.len == 0 { return error('no row') } return rows[0] } pub fn (db DB) exec_one(query string) ?Row { res := C.PQexec(db.conn, query.str) e := unsafe { C.PQerrorMessage(db.conn).vstring() } if e != '' { return error('pg exec error: "$e"') } row := rows_first_or_empty( res_to_rows(res) )? return row } // The entire function can be considered unsafe because of the malloc and the // free. This prevents warnings and doesn't seem to affect behavior. pub fn (db DB) exec_param_many(query string, params []string) []Row { unsafe { mut param_vals := &byteptr(malloc(params.len * 8)) for i in 0..params.len { param_vals[i] = params[i].str } res := C.PQexecParams(db.conn, query.str, params.len, 0, param_vals, 0, 0, 0) free(param_vals) return db.handle_error_or_result(res, 'exec_param_many') } } pub fn (db DB) exec_param2(query string, param, param2 string) []Row { mut param_vals := [2]byteptr{} param_vals[0] = param.str param_vals[1] = param2.str res := C.PQexecParams(db.conn, query.str, 2, 0, param_vals, 0, 0, 0) return db.handle_error_or_result(res, 'exec_param2') } pub fn (db DB) exec_param(query string, param string) []Row { mut param_vals := [1]byteptr{} param_vals[0] = param.str res := C.PQexecParams(db.conn, query.str, 1, 0, param_vals, 0, 0, 0) return db.handle_error_or_result(res, 'exec_param') } fn (db DB) handle_error_or_result(res voidptr, elabel string) []Row { e := unsafe { C.PQerrorMessage(db.conn).vstring() } if e != '' { println('pg $elabel error:') println(e) return res_to_rows(res) } return res_to_rows(res) }