GolangのORM、Xormの使い方ざっくりまとめ
最近、GolangのORMとしてXormを使っています。GoのORMといえばGormが主流で、調べてもXormに関する記事が少なかったので、備忘録としてまとめてみました。
自分が業務で使用している機能のみをまとめたので、さらに詳しく知りたい場合は公式ドキュメントを参照ください。
環境
- Go : 1.13.1
- Xorm : 0.7.9
- PostgreSQL : 11.6
使用するテーブル
| create table users( | |
| id BIGSERIAL, | |
| first_name VARCHAR(20) NOT NULL, | |
| last_name VARCHAR(20) NOT NULL, | |
| first_name_kana VARCHAR(20), | |
| last_name_kana VARCHAR(20), | |
| version BIGINT NOT NULL, | |
| created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, | |
| deleted_at TIMESTAMP, | |
| PRIMARY KEY (id) | |
| ); | |
| create table friends( | |
| id BIGSERIAL, | |
| user_id BIGINT NOT NULL, | |
| friend_id BIGINT NOT NULL, | |
| version BIGINT NOT NULL, | |
| created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, | |
| updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, | |
| deleted_at TIMESTAMP, | |
| PRIMARY KEY (id), | |
| FOREIGN KEY (user_id) REFERENCES users (id), | |
| FOREIGN KEY (friend_id) REFERENCES users (id) | |
| ); |
ユーザー情報を保存するusersテーブルと、ユーザー同士のフレンド関係を登録するfiriendsテーブルを作成しました 。
接続
| package main | |
| import ( | |
| "log" | |
| "github.com/go-xorm/xorm" | |
| _ "github.com/lib/pq" | |
| "xorm.io/core" | |
| ) | |
| func main() { | |
| engine, err := xorm.NewEngine("postgres", "postgres://user:password@db/database?sslmode=disable") | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| defer engine.Close() | |
| engine.ShowSQL(true) | |
| engine.SetMapper(core.GonicMapper{}) | |
| } |
engine.ShowSQL(true) を設定すると、実行されたSQLを標準出力に出すことができます。engine.SetMapper(core.GonicMapper{}) は、後述するフィールド名とカラム名のマッピングに影響します。
Create
| type user struct { | |
| ID uint64 `xorm:"autoincr"` | |
| FirstName string | |
| LastName string | |
| FirstNameKana string | |
| LastNameKana string | |
| Version uint64 `xorm:"version"` | |
| CreatedAt time.Time `xorm:"created"` | |
| UpdatedAt time.Time `xorm:"updated"` | |
| DeletedAt time.Time `xorm:"deleted"` | |
| } | |
| func (user) TableName() string { | |
| return "users" | |
| } | |
| func create(engine *xorm.Engine) { | |
| u := user{ | |
| FirstName: "太郎", | |
| LastName: "テスト", | |
| } | |
| affected, err := engine.Insert(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("No Affected") | |
| return | |
| } | |
| } |
INSERT INTO "users"(
"first_name",
"last_name",
"first_name_kana",
"last_name_kana",
"version",
"created_at",
"updated_at"
)
VALUES(
$1,
$2,
$3,
$4,
$5,
$6,
$7
) RETURNING "id"構造体のフィールド名をスネークケースに変換したものがカラム名に対応しています(FirstName -> first_name)。通常はフィールド名をID とするとi_d というカラムにマッピングしてしまいますが、engine.SetMapper(core.GonicMapper{}) を指定するとid にマッピングできます。フィールド名をId にするとGonicMapperは不要になりますが、Go のlintで警告が出てしまいます。
`xorm:"first_name"`のようにタグでカラム名を指定することもできますが、全てのフィールドに指定するのは面倒なのでフィールド名を合わせた方が良いと思います。
versionカラムは楽観的ロックのために使用します。

xormではDBにversionカラムを設定し、フィールドに `xorm:"version"` を指定するとupdate時に自動で判定してくれます。
大抵のテーブルでidカラムは自動採番だと思います。構造体のIDフィールドのタグに `xorm:"autoincr"` をつけると、insert対象のカラムからidが外され、SQLにRETURNING "id" が追加され、insert後のインスタンスにinsert結果のidが自動で設定されます。自動採番されたidを別のテーブルに登録する際に使えます。
| type friend struct { | |
| ID uint64 `xorm:"autoincr"` | |
| UserID uint64 | |
| FriendID uint64 | |
| Version uint64 `xorm:"version"` | |
| CreatedAt time.Time `xorm:"created"` | |
| UpdatedAt time.Time `xorm:"updated"` | |
| DeletedAt time.Time `xorm:"deleted"` | |
| } | |
| func (friend) TableName() string { | |
| return "friends" | |
| } | |
| { | |
| u2 := user{ | |
| FirstName: "太郎2", | |
| LastName: "テスト", | |
| } | |
| affected, err = engine.Insert(&u2) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("No Affected") | |
| return | |
| } | |
| f := friend{ | |
| UserID: u.ID, | |
| FriendID: u2.ID, | |
| } | |
| affected, err = engine.Insert(&f) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("No Affected") | |
| return | |
| } | |
| } |
insertでスライスを渡せば、一括登録もできます。
| us := []user{ | |
| { | |
| FirstName: "太郎3", | |
| LastName: "テスト", | |
| }, | |
| { | |
| FirstName: "太郎4", | |
| LastName: "テスト", | |
| }, | |
| } | |
| affected, err = engine.Insert(&us) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected != 2 { | |
| log.Println("Unexpected Affected") | |
| return | |
| } |
INSERT INTO "users"(
"first_name",
"last_name",
"first_name_kana",
"last_name_kana",
"version",
"created_at",
"updated_at"
)
VALUES(
$1,
$2,
$3,
$4,
$5,
$6,
$7
),
(
$8,
$9,
$10,
$11,
$12,
$13,
$14
)一括の場合はidがインスタンスに自動でセットされないので、必要であれば検索するか、単一のinsertを使用することになります。
Read
| func read(engine *xorm.Engine) { | |
| t := time.Date(2020, 1, 1, 0, 0, 0, 0, time.Local) | |
| // 1件取得 | |
| u := user{} | |
| has, err := engine. | |
| Where("id = ?", id). | |
| Where("last_name LIKE ?", "テスト"). | |
| In("last_name", "テスト", "テスト2"). | |
| In("last_name", []string{"テスト", "テスト2"}). | |
| Where("first_name = ? OR first_name = ?", "太郎", "太郎2"). | |
| Where("created_at >= ?", t). | |
| Get(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if !has { | |
| log.Println("Not Found") | |
| return | |
| } | |
| // 複数取得 | |
| us := []user{} | |
| err = engine. | |
| Limit(10, 10). | |
| Asc("id", "first_name"). | |
| Desc("last_name"). | |
| Find(&us) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| } |
Get で1件のみ、Find で複数検索です。
Get
SELECT
"id",
"first_name",
"last_name",
"first_name_kana",
"last_name_kana",
"version",
"created_at",
"updated_at",
"deleted_at"
FROM
"users"
WHERE
(
id = $1
)
AND (last_name LIKE $2)
AND "last_name" IN($3, $4)
AND "last_name" IN($5, $6)
AND (
first_name = $7
OR first_name = $8
)
AND (
created_at >= $9
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $10
)
LIMIT 1Find
SELECT
"id",
"first_name",
"last_name",
"first_name_kana",
"last_name_kana",
"version",
"created_at",
"updated_at",
"deleted_at"
FROM
"users"
WHERE
(
"deleted_at" IS NULL
OR "deleted_at" = $1
)
ORDER BY
"id" ASC,
"first_name" ASC,
"last_name" DESC
LIMIT 10 OFFSET 10構造体のフィールドに `xorm:"deleted"` があれば、where句に自動で
("deleted_at" IS NULL OR "deleted_at" = '0001-01-01 00:00:00')が追加されます。deleted_atが(NULL or ゼロ値)であることを確認しています。
Unscoped() でdeleted_atを無視できます。
| us := []user{} | |
| err = engine. | |
| Unscoped(). | |
| Find(&us) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } |
SELECT
"id",
"first_name",
"last_name",
"first_name_kana",
"last_name_kana",
"version",
"created_at",
"updated_at",
"deleted_at"
FROM
"users"countもできます。
| u := user{} | |
| count, err := engine.Count(&u2) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } |
SELECT
count(*)
FROM
"users"
WHERE
(
"deleted_at" IS NULL
OR "deleted_at" = $1
)FindAndCount() でFindとCountを同時に実行できます。
| us := []user{} | |
| count, err = engine. | |
| Limit(10, 10). | |
| Asc("id", "first_name"). | |
| Desc("last_name"). | |
| FindAndCount(&us3) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } |
SELECT
"id",
"first_name",
"last_name",
"first_name_kana",
"last_name_kana",
"version",
"created_at",
"updated_at",
"deleted_at"
FROM
"users"
WHERE
(
"deleted_at" IS NULL
OR "deleted_at" = $1
)
ORDER BY
"id" ASC,
"first_name" ASC,
"last_name" DESC
LIMIT 10 OFFSET 10
SELECT
count(*)
FROM
"users"
WHERE
(
"deleted_at" IS NULL
OR "deleted_at" = $1
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $2
)2つのSQLが実行されます。limitやoffsetを設定していてもcountでは無視されます。countのdeleted_atの部分が重複しているのはバグな気がしますが、結果には影響ないので多分大丈夫です。
joinした結果を取得したい場合、新たに構造体を定義します。
| type joinedUser struct { | |
| User user `xorm:"extends"` | |
| Friend friend `xorm:"extends"` | |
| } | |
| func (joinedUser) TableName() string { | |
| return "friends" | |
| } | |
| { | |
| jus := []joinedUser{} | |
| err = engine. | |
| Join("INNER", "users", "users.id = friends.friend_id"). | |
| Find(&jus) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| } |
SELECT
*
FROM
"friends"
INNER JOIN
users
ON users.id = friends.friend_id
WHERE
(
"friends"."deleted_at" IS NULL
OR "friends"."deleted_at" = $1
)フィールドに`xorm:"extends"` を設定します。from句のdeleted_atは自動で確認してくれますが、join先のdeleted_atは確認してくれないので、自分で追加する必要があります。
サブクエリを設定する場合は"xorm.io/builder" を使います。
| subQuery := builder. | |
| Select("id"). | |
| From("users"). | |
| Where(builder.Eq{"last_name": "テスト"}). | |
| Where(builder.Like{"last_name", "テスト"}). | |
| Where(builder.In("last_name", "テスト", "テスト2")). | |
| Where(builder.In("last_name", []string{"テスト", "テスト2"})). | |
| Where(builder.Eq{"first_name": "太郎2"}. | |
| Or(builder.Eq{"first_name": "太郎3"}). | |
| Or(builder.Eq{"first_name": "太郎4"}), | |
| ). | |
| Where(builder.Gte{"created_at": t}). | |
| Where(builder.IsNull{"deleted_at"}) | |
| fs := []friend{} | |
| err = engine. | |
| In("friend_id", subQuery). | |
| Limit(10, 10). | |
| Asc("id", "friend_id"). | |
| Desc("user_id"). | |
| Find(&fs) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } |
SELECT
"id",
"user_id",
"friend_id",
"version",
"created_at",
"updated_at",
"deleted_at"
FROM
"friends"
WHERE
"friend_id" IN(
SELECT
id
FROM
users
WHERE
last_name = $1
AND last_name LIKE $2
AND last_name IN($3, $4)
AND last_name IN($5, $6)
AND (
first_name = $7
OR first_name = $8
OR first_name = $9
)
AND created_at >= $10
AND deleted_at IS NULL
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $11
)
ORDER BY
"id" ASC,
"friend_id" ASC,
"user_id" DESC
LIMIT 10 OFFSET 10サブクエリはjoin句にも設定できます。
| jus := []joinedUser{} | |
| err = engine. | |
| Join("INNER", subQuery, "users.id = friends.friend_id"). | |
| Find(&jus) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } |
SELECT
*
FROM
"friends"
INNER JOIN
(
SELECT
id
FROM
users
WHERE
last_name = $1
AND last_name LIKE $2
AND last_name IN($3, $4)
AND last_name IN($5, $6)
AND (
first_name = $7
OR first_name = $8
OR first_name = $9
)
AND created_at >= $10
AND deleted_at IS NULL
) users
ON users.id = friends.friend_id
WHERE
(
"friends"."deleted_at" IS NULL
OR "friends"."deleted_at" = $11
)集約してcountする場合も構造体を定義します。
| type countPerUserID struct { | |
| Count uint | |
| UserID uint64 | |
| } | |
| func (countPerUserID) TableName() string { | |
| return "friends" | |
| } | |
| { | |
| cpuis := []countPerUserID{} | |
| err = engine. | |
| Select("count(*) as count, user_id"). | |
| Where("deleted_at IS NULL"). | |
| GroupBy("user_id"). | |
| Find(&cpuis) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| } |
SELECT
count(*) as count,
user_id
FROM
"friends"
WHERE
(deleted_at IS NULL)
GROUP BY
user_id欲しいカラムが1つだけの場合はプリミティブ型をそのまま使うこともできます。
| var friendID uint64 | |
| has, err = engine. | |
| Table("friends"). | |
| Where("user_id = ?", id). | |
| Where("deleted_at IS NULL"). | |
| Desc("created_at"). | |
| Cols("friend_id"). | |
| Get(&friendID) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if !has { | |
| log.Println("Not Found") | |
| return | |
| } |
SELECT
"friend_id"
FROM
"friends"
WHERE
(
user_id = $1
)
AND (deleted_at IS NULL)
ORDER BY
"created_at" DESC
LIMIT 1Update
| u := user{} | |
| has, err := engine. | |
| Where("id = ?", id). | |
| Get(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if !has { | |
| log.Println("Not Found") | |
| return | |
| } | |
| u.FirstName = "太郎更新" | |
| u.LastName = "テスト更新" | |
| u.FirstNameKana = "タロウコウシン" | |
| affected, err = engine. | |
| Where("id = ?", id). | |
| Update(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Not Found") | |
| return | |
| } |
UPDATE
"users"
SET
"first_name" = $1,
"last_name" = $2,
"first_name_kana" = $3,
"updated_at" = $4,
"version" = "version" + 1
WHERE
(
id = $5
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $6
)
AND "version" = $7version を自動で確認・更新してくれます。更新後のインスタンスはu.Version とu.UpdatedAt が更新されています。
DBのversion が変わっていた場合は更新対象が0件(affected == 0)になります。
| u.Version = 9999 | |
| affected, err = engine. | |
| Where("id = ?", id). | |
| Update(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Optimistic Lock") | |
| } |
楽観的ロックを使用せず、version がない場合は普通に更新されます。
| type userForNoLock struct { | |
| ID uint64 `xorm:"autoincr"` | |
| FirstName string | |
| LastName string | |
| FirstNameKana string | |
| LastNameKana string | |
| CreatedAt time.Time `xorm:"created"` | |
| UpdatedAt time.Time `xorm:"updated"` | |
| DeletedAt time.Time `xorm:"deleted"` | |
| } | |
| func (userForNoLock) TableName() string { | |
| return "users" | |
| } | |
| { | |
| ufnl := userForNoLock{ | |
| FirstName: "太郎更新", | |
| FirstNameKana: "タロウコウシン", | |
| } | |
| affected, err := engine. | |
| Where("id = ?", id). | |
| Update(&ufnl) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Not Found") | |
| return | |
| } | |
| } |
UPDATE
"users"
SET
"first_name" = $1,
"first_name_kana" = $2,
"updated_at" = $3
WHERE
(
id = $4
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $5
)インスタンスのフィールドがゼロ値の場合は、更新対象のカラムに含まれません。
| u.FirstName = "" | |
| u.LastName = "" | |
| u.FirstNameKana = "" | |
| affected, err = engine. | |
| Where("id = ?", id). | |
| Update(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Not Found") | |
| return | |
| } |
UPDATE
"users"
SET
"updated_at" = $1,
"version" = "version" + 1
WHERE
(
id = $2
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $3
)
AND "version" = $4Cols で指定すると、インスタンスのフィールドがゼロ値でも更新できます。ただし、指定したカラムとupdated_at,versionしか更新されなくなります。
| u.FirstName = "" | |
| u.LastName = "" | |
| u.FirstNameKana = "更新されない" | |
| affected, err = engine. | |
| Cols("first_name"). | |
| Where("id = ?", id). | |
| Update(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Not Found") | |
| return | |
| } |
UPDATE
"users"
SET
"first_name" = $1,
"updated_at" = $2,
"version" = "version" + 1
WHERE
(
id = $3
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $4
)
AND "version" = $5AllCols() で全カラムを更新できます。
| u.FirstName = "" | |
| u.LastName = "" | |
| u.FirstNameKana = "" | |
| affected, err = engine. | |
| AllCols(). | |
| Where("id = ?", id). | |
| Update(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } |
UPDATE
"users"
SET
"first_name" = $1,
"last_name" = $2,
"first_name_kana" = $3,
"last_name_kana" = $4,
"updated_at" = $5,
"version" = "version" + 1
WHERE
(
id = $6
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $7
)
AND "version" = $8構造体のフィールドをポインタで定義すると (ゼロ値 = nil) となるため、空文字や0のポインタをセットすれば更新できるようになります。
| type userForEnableEmpty struct { | |
| ID uint64 `xorm:"autoincr"` | |
| FirstName *string | |
| LastName *string | |
| FirstNameKana *string | |
| LastNameKana *string | |
| Version uint64 `xorm:"version"` | |
| CreatedAt time.Time `xorm:"created"` | |
| UpdatedAt time.Time `xorm:"updated"` | |
| DeletedAt time.Time `xorm:"deleted"` | |
| } | |
| func (userForEnableEmpty) TableName() string { | |
| return "users" | |
| } | |
| { | |
| ufee := userForEnableEmpty{} | |
| has, err = engine. | |
| Where("id = ?", id). | |
| Get(&ufee) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if !has { | |
| log.Println("Not Found") | |
| return | |
| } | |
| ufee.FirstName = &u.FirstName | |
| ufee.LastName = &u.LastName | |
| ufee.FirstNameKana = &u.FirstNameKana | |
| affected, err = engine. | |
| Where("id = ?", id). | |
| Update(&ufee) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Not Found") | |
| return | |
| } | |
| } |
UPDATE
"users"
SET
"first_name" = $1,
"last_name" = $2,
"first_name_kana" = $3,
"last_name_kana" = $4,
"updated_at" = $5,
"version" = "version" + 1
WHERE
(
id = $6
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $7
)
AND "version" = $8ポインタとCols() を組み合わせるとNULLで更新できます。
| ufee.FirstName = nil | |
| ufee.LastName = nil | |
| ufee.FirstNameKana = nil | |
| affected, err = engine. | |
| Cols("first_name_kana"). | |
| Where("id = ?", id). | |
| Update(&ufee) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Not Found") | |
| return | |
| } |
UPDATE
"users"
SET
"first_name_kana" = $1,
"updated_at" = $2,
"version" = "version" + 1
WHERE
(
id = $3
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $4
)
AND "version" = $5Cols()とNullable()を組み合わせてもNULL更新できます。
| u.FirstNameKana = "" | |
| affected, err = engine. | |
| Cols("first_name_kana"). | |
| Nullable("first_name_kana"). | |
| Where("id = ?", id). | |
| Update(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } |
UPDATE
"users"
SET
"first_name_kana" = $1,
"updated_at" = $2,
"version" = "version" + 1
WHERE
(
id = $3
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $4
)
AND "version" = $5Delete
| func delete(engine *xorm.Engine) { | |
| u := user{} | |
| affected, err := engine. | |
| Where("id = ?", id). | |
| Delete(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Not Found") | |
| return | |
| } | |
| } |
UPDATE
"users"
SET
"deleted_at" = $1
WHERE
(
id = $2
)
AND (
"deleted_at" IS NULL
OR "deleted_at" = $3
)構造体に`xorm:"deleted"` があれば論理削除、なければ物理削除になります。フィールドのタグで判定しているので、DeletedAt というフィールドがあっても、タグがなければ物理削除です。
| type friendForPhysicalDelete struct { | |
| DeletedAt time.Time | |
| } | |
| func (friendForPhysicalDelete) TableName() string { | |
| return "friends" | |
| } | |
| { | |
| f := friendForPhysicalDelete{} | |
| affected, err = engine. | |
| Where("user_id = ? OR friend_id = ?", id, id). | |
| Delete(&f) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Not Found") | |
| return | |
| } | |
| } |
DELETE
FROM
"friends"
WHERE
(
user_id = $1
OR friend_id = $2
)Unscoped() でも物理削除できます。
| affected, err = engine. | |
| Unscoped(). | |
| Where("id = ?", id). | |
| Delete(&u) | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| if affected == 0 { | |
| log.Println("Not Found") | |
| return | |
| } |
DELETE
FROM
"users"
WHERE
(
id = $1
)
AND "deleted_at" = $2その他
Exec()でCRUD以外も実行できます。
| _, err := engine.Exec("CREATE ROLE role_name LOGIN PASSWORD 'password'") | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } | |
| _, err = engine.Exec("DROP ROLE role_name") | |
| if err != nil { | |
| log.Println(err) | |
| return | |
| } |
CREATE ROLE role_name LOGIN PASSWORD 'password'
DROP ROLE role_name以上です。他にいい感じの機能ががあれば追記するかもです。

