在 raw SQL 中指定 table name 和 index name

GORM 支持使用 raw SQL,不过有个问题,就是使用字符串传递 index name 或者 table name 时,会被自动加上单引号 (Postgres driver)。例如下面的代码:

// DB wraps gorm.DB object to provides more methods.
type DB struct {
	*gorm.DB
}

func (db *DB) CreateBucketObjectTable(bucket *Bucket) (err error) {
	tableName := bucket.ObjectTableName()

	if err = db.Table(tableName).Migrator().CreateTable(new(Object)); err != nil {
		return err
	}
	err = db.Exec(`CREATE UNIQUE INDEX ? ON ? ("key")`, 
		fmt.Sprintf("idx_%s_key", tableName), tableName).Error
	if err != nil {
		return nil
	}

	return nil
}

最终会执行的 SQL 语句是

CREATE UNIQUE INDEX 'idx_bucket_1_key' ON 'bucket_1' ("key");

因为 index name 和 table name 都被自动加上了单引号,所以数据库那边会返回错误,类似: ERROR: syntax error at or near “$1” (SQLSTATE 42601)

这个问题是因为 GORM 为了安全起见,在生成语句时,如果 ? 占位符对应的是字符串,都自动加上了单引号。解决这个问题的办法是使用 gorm/clause 中的数据类型,如下所示:

import (
	"fmt"

	"gorm.io/gorm/clause"
)

func (db *DB) CreateBucketObjectTable(bucket *Bucket) (err error) {
	tableName := bucket.ObjectTableName()

	if err = db.Table(tableName).Migrator().CreateTable(new(Object)); err != nil {
		return err
	}
	index := clause.Table{Name: fmt.Sprintf("idx_%s_key", tableName)}
	table := clause.Table{Name: tableName}
	err = db.Exec(`CREATE UNIQUE INDEX ? ON ? ("key")`, index, table).Error
	if err != nil {
		return err
	}

	return nil
}

这里我们明确了 ? 占位符对应的是表名,所以生成的语句会变成:

CREATE UNIQUE INDEX "idx_bucket_1_key" ON "bucket_1" ("key");

知识共享许可协议本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。