11
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

HubbleAdvent Calendar 2023

Day 5

gormのDB.Set関数利用時のエラーについて

Last updated at Posted at 2023-12-05

これはHubble Advent Calendar 2023の3日目1の記事です。

はじめに

契約書の作成から管理までを行える契約書管理クラウドのHubbleでは、一部のサービスをGo言語で実装しています。
ORMにgormを使っているサービスがあり、その利用シーンにおいて問題が発生し、解決に時間を要したため、この記事にて紹介させていただきます。

gormのDB.Set関数の振る舞いについて

サンプル

sample.go
const key = "test_key"

type (
	Form struct {
		ID                  uint                 `json:"id" gorm:"primaryKey"`
		Name                string               `json:"name" gorm:"not null"`
		IsAllUserAuthorized *bool                `json:"isAllUserAuthorized" gorm:"default:false"`
		FormAuthorizedTeams []FormAuthorizedTeam `json:"formAuthorizedTeams" gorm:"foreignKey:FormID"`
	}
	FormAuthorizedTeam struct {
		ID     uint   `json:"id" gorm:"primaryKey"`
		FormID uint   `json:"formId" gorm:"not null"`
		TeamID string `json:"teamId" gorm:"not null"`
	}
)

func (f *Form) AfterFind(tx *gorm.DB) error {
	v, ok := tx.Get(key)
	if ok {
		fmt.Printf("Form:%d\n", v)
	} else {
		fmt.Println("xxxx")
	}
	return nil
}

func (f *FormAuthorizedTeam) AfterFind(tx *gorm.DB) error {
	v, ok := tx.Get(key)
	if ok {
		fmt.Printf("FormAuthorizedTeam:%s\n", v)
	} else {
		fmt.Println("xxxx")
	}
	return nil
}

func IsAuthorized(tx *gorm.DB, formID uint, teamIDs []string) error {
	tx = tx.Set(key, fmt.Sprintf("form_id:%d", formID))

	subQuery := tx.Model(&FormAuthorizedTeam{}).
		Where("team_id IN (?)", teamIDs).Select("form_id")

	return tx.Table("forms").Where("id", formID).
		Where(
			tx.Or("is_all_user_authorized", 1).
				Or("id IN (?)", subQuery),
		).First(&Form{}).Error
}

上記を実行したところ、下記のようにSQLのシンタックスエラーが発生してしまいました。

エラー内容
 Error 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')) ORDER BY `forms`.`id` LIMIT 1' at line 1

余談ですが、このサンプルにおいて、メインクエリでDebug()を指定すると、Stack Overflowとなり、Access Violationが発生してしまいました。

	return tx.Debug().Set(key, fmt.Sprintf("form_id:%d", formID)).Table("forms").Where("id", formID).
		Where(
			tx.Or("is_all_user_authorized", 1).
				Or("id IN (?)", subQuery),
		).First(&Form{}).Error

SQL文について

上記のサンプルにおいて、下記のクエリが実行されることを想定していました。

期待したSQL
SELECT * FROM `forms` WHERE `id` = 864322 AND (`is_all_user_authorized` = 1 OR id IN (SELECT `form_id` FROM `form_authorized_teams` WHERE team_id IN ('IsAuthorizedWithTx_team_id'))) ORDER BY `forms`.`id` LIMIT 1

しかしながら、実際のクエリは下記となり、subQueryで指定したサブクエリにメインクエリがいりじ混じったクエリとなってしまいました。

実際のSQL
SELECT `form_id` FROM `forms` WHERE (team_id IN ('IsAuthorizedWithTx_team_id') AND `id` = 546285 OR `is_all_user_authorized` = 1 OR id IN (SELECT `form_id` FROM `forms` WHERE (team_id IN ('IsAuthorizedWithTx_team_id') AND `id` = 546285 OR `is_all_user_authorized` = 1 OR id IN ()) ORDER BY `forms`.`id` LIMIT 1

解決方法

結論から、IsAuthorized関数を下記のように、メインクエリにSet関数を指定するように修正することで、期待通りの動作となりました。

IsAuthorized
func IsAuthorized(tx *gorm.DB, formID uint, teamIDs []string) error {
	subQuery := tx.Model(&FormAuthorizedTeam{}).
		Where("team_id IN (?)", teamIDs).Select("form_id")

	return tx.Set(key, fmt.Sprintf("form_id:%d", formID)).Table("forms").Where("id", formID).
		Where(
			tx.Or("is_all_user_authorized", 1).
				Or("id IN (?)", subQuery),
		).First(&Form{}).Error
}

*gorm.DBのオブジェクトを引数で受け渡す前に、Set関数が指定されるケースがあり、今回のエラーが発生しておりました。
なお、下記のようにtx.Session(&gorm.Session{})とセッションを生成することでも回避できましたので、*gorm.DBのオブジェクトを引数で受け渡す前に、Set関数が必要であれば、この方法が良いかもしれません。

IsAuthorized
func IsAuthorized(tx *gorm.DB, formID uint, teamIDs []string) error {
	tx = tx.Set(key, fmt.Sprintf("form_id:%d", formID))

	subQuery := tx.Session(&gorm.Session{}).Model(&FormAuthorizedTeam{}).
		Where("team_id IN (?)", teamIDs).Select("form_id")

	return tx.Table("forms").Where("id", formID).
		Where(
			tx.Or("is_all_user_authorized", 1).
				Or("id IN (?)", subQuery),
		).First(&Form{}).Error
}

備考

gormHooksについて

gormには、Hooksという機能があります。
上記のサンプルでは、formモデルに対し、SELECTが施されると、func (f *Form) AfterFind(tx *gorm.DB) errorが呼び出され、下記のように出力されます。

出力結果
Form:form_id:704764

ただし、このケースでは、サブクエリも施されているため、func (f *FormAuthorizedTeam) AfterFind(tx *gorm.DB) errorが呼ばれる想定しておりました。サブクエリでhooksが機能するかどうか、引き続き調査中です。

明日は@taamoo2さんの投稿を予定しております。

  1. 平日のみの投稿なので、投稿日は5日ですが3日目の記事としています。

11
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?