🗝️

xo の外部キー関数を uuid.NullUUID に対応させる

2023/04/22に公開

xo は PostgreSQL の UUID 型に対応しているが、nullable の UUID 型 (uuid.NullUUID) を参照する外部キー関数はうまく生成できない。

例えば、このようなスキーマから xo でコードを生成した場合、以下のような関数が生成される。

CREATE TABLE areas (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    name VARCHAR(255) NOT NULL
);

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    area_id UUID REFERENCES areas (id) ON DELETE RESTRICT
);
user.xo.go
// Area returns the Area associated with the User's (AreaID).
//
// Generated from foreign key 'users_area_id_fkey'.
func (u *User) Area(ctx context.Context, db DB) (*Area, error) {
	return AreaByAreaID(ctx, db, uuid.UUID(u.AreaID)) // <- 👎
}

u.AreaID は uuid.NullUUID であるため、 uuid.UUID(u.AreaID) となっている部分でエラーが発生する。そのため、カスタムテンプレートを使用して関数を修正する必要がある。

cannot convert u.AreaID (variable of type uuid.NullUUID) to type uuid.UUID

解決策

まず、 xo の template を dump する。

xo dump xo/templates

templates/go.go にある convertTypes() を変更する。

xo/templates/go.go
func (f *Funcs) convertTypes(fkey ForeignKey) string {
	var p []string
	for i := range fkey.Fields {
		field := fkey.Fields[i]
		refField := fkey.RefFields[i]
		expr := f.short(fkey.Table) + "." + field.GoName
		// types match, can match
		if field.Type == refField.Type {
			p = append(p, expr)
			continue
		}
		// convert types
		typ, refType := field.Type, refField.Type
		if strings.HasPrefix(typ, "sql.Null") {
			expr = expr + "." + typ[8:]
			typ = strings.ToLower(typ[8:])
		} else if typ == "uuid.NullUUID" { // <- ここを追加
			expr = expr + ".UUID"
			typ = strings.ToLower("uuid.UUID")
		}
		if strings.ToLower(refType) != typ {
			expr = refType + "(" + expr + ")"
		}
		p = append(p, expr)
	}
	return strings.Join(p, ", ")
}

編集したカスタムテンプレートを使用してコードを再生成する。

xo schema --src xo/templates
user.xo.go
// Area returns the Area associated with the User's (AreaID).
//
// Generated from foreign key 'users_area_id_fkey'.
func (u *User) Area(ctx context.Context, db DB) (*Area, error) {
	return AreaByAreaID(ctx, db, u.AreaID.UUID) // <- 👍
}

Discussion