GO 言語で PostgreSQL につなぐ

早速、実用に使いたいので、GO 言語(golang)で PostgreSQL につないでみたくなった。


課題:
 1.GO 言語から、PostgreSQL につなぐ。
PostgreSQL bindings for golang


 2.そのためには、GO 言語から、外部の C ライブラリを呼び出す必要がある。
C library(libpq) binding for golang.


いい加減だが、なんとかクリア。



追記:github においてみた。
I put PostgreSQL binding for golang to github. --> github:go-pg

$ git clone git://github.com/oibore/go-pg.git go-pg
$ cd go-pg/src
$ make
$ ./runnner


まず、C で PostgrSQL のライブラリ libpq の Wrapper。

$ cat pg_wrapper.h
#ifndef __PG_WRAPPER_H__
#define __PG_WRAPPER_H__

void *PgConnectDb(const char* conninfo);
void PgFinish(void *conn);
int  PgStatus(void *conn);
void *PgExec(void *conn, const char *command);
void *PgGetResult(void *conn);
int  PgNFields(void *res);
int  PgNTuples(void *res);
int  PgGetIsNull(void *res, int row_number, int column_number);
char *PgGetValue(void *res, int row_number, int column_number);

#endif /* __PG_WRAPPER_H__ */

$ cat pg_wrapper.c
#include 
#include "pg_wrapper.h"

void *PgConnectDb(const char *conninfo)
{
    PGconn *conn = PQconnectdb(conninfo);
    return conn;
}

void PgFinish(void *conn)
{
    PQfinish(conn);
}

int PgStatus(void *conn)
{
    return PQstatus(conn);
}

void *PgExec(void *conn, const char *command)
{
    return PQexec(conn, command);
}

void *PgGetResult(void *conn)
{
    return PQgetResult(conn);
}

int PgNFields(void *res)
{
    return PQnfields(res);
}

int PgNTuples(void *res)
{
    return PQntuples(res);
}

int PgGetIsNull(void *res, int row_number, int column_number)
{
    return PQgetisnull(res, row_number, column_number);
}

char *PgGetValue(void *res, int row_number, int column_number)
{
    return PQgetvalue(res, row_number, column_number);
}

次に、GO の import する pkg(?)。

$ cat pg.go
package pg

// #include "pg_wrapper.h"
import "C"

import (
    "unsafe";
)

func Connect(conninfo string) unsafe.Pointer {
    conn := C.PgConnectDb(C.CString(conninfo));
    return conn;
}

func Close(conn unsafe.Pointer) {
    C.PgFinish(conn);
}

func Status(conn unsafe.Pointer) int {
    status := C.PgStatus(conn);
    return int(status);
}

func Exec(conn unsafe.Pointer, command string) unsafe.Pointer {
    res := C.PgExec(conn, C.CString(command));
    return res;
}

func GetResult(conn unsafe.Pointer) unsafe.Pointer {
    return C.PgGetResult(conn);
}

func NFields(res unsafe.Pointer) int {
    return int(C.PgNFields(res));
}

func NTuples(res unsafe.Pointer) int {
    return int(C.PgNTuples(res));
}

func GetIsNull(res unsafe.Pointer, row_number int, column_number int) int {
    return int(C.PgGetIsNull(res, _C_int(row_number), 
                                  _C_int(column_number)));
}

func GetValue(res unsafe.Pointer, row_number int, column_number int) string {
    value := C.GoString(C.PgGetValue(res, _C_int(row_number), 
                                        _C_int(column_number)));
    return value;
}

ほんで、本体となる go スクリプト

$ cat runner.go
package main

import (
  "fmt";
  "pg";
)

func main() {
  conninfo := "dbname=testdb";
  conn := pg.Connect(conninfo);

  status := pg.Status(conn);
  fmt.Printf("conninfo=%s, status=%d\n", conninfo, status);

  res := pg.Exec(conn, "select * from users;");
  //res := pg.GetResult(conn);

  fileds := pg.NFields(res);
  fmt.Printf("fields = %d\n", fileds);

  value := pg.GetValue(res, 0, 0);
  fmt.Printf("value = %s\n", value);

  pg.Close(conn);
}

それと、Makefile。これは i386, Linux 用なので、64な人とか、MacOS X などは適宜変更すること。
You need to change Makefile for $GOARCH and $GOOS.

$ Makefile
include $(GOROOT)/src/Make.$(GOARCH)

TARG=pg

CGOFILES=pg.go
CGO_LDFLAGS=pg_wrapper.o -lpq
CLEANERFILES+=runner

include $(GOROOT)/src/Make.pkg

all: runner

runner: pg_wrapper.o install runner.go
	$(GC) runner.go
	$(LD) -o $@ runner.$O

pg_wrapper.o: pg_wrapper.c
	gcc -fPIC -O2 -o pg_wrapper.o -c pg_wrapper.c

あとは make すればよし。

[oibore@dural pg]$ ls
Makefile  pg.go  pg_wrapper.c  pg_wrapper.h  runner.go  work
[oibore@dural pg]$ make
cgo  pg.go
8g -o _go_.8 pg.cgo1.go pg.cgo2.go
8c -FVw -I/home/oibore/go/src/pkg/runtime  pg.cgo3.c
rm -f _obj/pg.a
gopack grc _obj/pg.a _go_.8 pg.cgo3.8
gcc -fPIC -O2 -o pg_wrapper.o -c pg_wrapper.c
cp _obj/pg.a /home/oibore/go/pkg/linux_386/pg.a
gcc -m32 -fPIC -O2 -o pg.cgo4.o -c  pg.cgo4.c
gcc -m32 -shared -lpthread -lm -o pg_pg.so pg.cgo4.o pg_wrapper.o -lpq
cp pg_pg.so /home/oibore/go/pkg/linux_386/./pg_pg.so
8g runner.go
8l -o runner runner.8
rm pg.cgo4.o pg_pg.so
[oibore@dural pg]$ ./runner
fields = 2
value = foo


面倒なので、ファイル一式アップしておく。


追記:
github においてみた。--> github:go-pg


まだ、複数行とタプルを連続してとれない。
select して、0行目の0番目のカラムみたいなかんじ。
まずは各 DB 毎にドライバかいて、いずれは DBI みたいなのを作るのかな。


ややこしかったところ:

1) C の Wrap で、相互に変数をやり取りするとき、
String の場合、 C -> GO は C.CString()、 GO -> C は C.GoString() をつかう。
int の場合、C -> GO は int()でいけるが、 GO -> C は _C_int() とする必要がある。


2) C のライブラリを呼び出すのは

// include "pg_wrapper.h"
import "C"

とか、やるらしい。


参考リンク
go で sqlite3
gmp.go
gotweet
libpq - C ライブラリ
CPAN Pg



海外から見に来る人が多いので、(下手な)英語を少し追加。