Closed4

PostgreSQLのDDLをGoから取得したい

tharutharu

PostgreSQLには SHOW CREATE TABLE のようなDDLを表示するステートメントがない。
代替として、How to generate the "create table" sql statement for an existing table in postgreSQL - Stack Overflow には2通りの方法が挙げられている。

  1. コマンドラインから pg_dump で出力する
  2. pg_catalog 内のテーブルを組み合わせた関数を作成して出力する

2つ目の方法はSQLで取得できるお手軽さがあるが、複雑なSQLを使用しているのでメンテが面倒。
1つ目の方法をGolangで実現する方法を考えていきたい。

以下のライブラリが参考になりそう。
https://github.com/habx/pg-commands

tharutharu

ライブラリの中でどのように pg_dump を実行しているか見てみたが、単に実行環境の pg_dump をコマンドで実行しているだけだった。

https://github.com/habx/pg-commands/blob/dev/pg_dump.go#L47

pg_dump 自体をライブラリ化しているGoのパッケージがないか探してみたが、今日(2022年5月6日)時点では存在していない。

pg_dump はC言語で書かれているので、GoからCの関数を実行できれば事前に pg_dump をインストールしておく必要もなくなりそう。(多分?)

個人的に未知の領域で時間がかかりそうなので、今回は habx/pg-commands と同じ手法を取る。

tharutharu

ちなみに pg_dump は内部でSELECT文を発行している模様。

PostgreSQL: Documentation: 14: pg_dump

pg_dump internally executes SELECT statements.

これの中身が分かれば簡単に再現できそうだが、情報が見つからなかった…。

tharutharu

結果、思いのほか単純な関数になった。
executePgDumpConnection String と特定のスキーマ名・テーブル名を引数に取る。
DB的にはテーブルを指定しない方がコール回数を抑えられるが、指定することで出力されるテキスト内の CREATE 文が1つに抑えられる(はず?)。こちらの方が、正規表現がシンプルになって良い。

package main

import (
	"bytes"
	"fmt"
	"os/exec"
	"regexp"
)

func setConStr(user string, pass string, host string, port string, db string) string {
	return fmt.Sprintf("sslmode=require user=%s password=%s host=%s port=%s dbname=%s connect_timeout=300", user, pass, host, port, db)

}

func executePgDump(conStr string, schema string, table string) (string, error) {
	t := fmt.Sprintf("\"%s\".\"%s\"", schema, table)
	args := []string{
		"-t", t,
		"--schema-only",
		"--quote-all-identifiers",
		"-d", conStr,
	}
	cmd := exec.Command("pg_dump", args...)
	out := bytes.Buffer{}
	stderr := bytes.Buffer{}
	cmd.Stdout = &out
	cmd.Stderr = &stderr
	err := cmd.Run()
	if err != nil {
		fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
		return "", err
	}
	ddl := parseDdl(out.Bytes())
	return ddl, nil
}

func parseDdl(dump []byte) string {
	r := regexp.MustCompile("(?mi)create.* (table|view|materialized view|)[^;]*;")
	ddl := r.Find(dump)
	return string(ddl)
}
このスクラップは2022/05/07にクローズされました