【问题标题】:How to write Scan() for custom type in PostgreSQL column in Go如何在 Go 的 PostgreSQL 列中为自定义类型编写 Scan()
【发布时间】:2021-10-28 05:09:56
【问题描述】:

所以我有这个表架构,如下所示

CREATE TABLE artists (
  id SERIAL PRIMARY KEY,
  foo_user_id TEXT NOT NULL,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
  time_span TIME_SPAN NOT NULL,
  items artist [] NOT NULL
);

另外,我还有在PostgreSQL中创建的自定义类型,定义如下

CREATE TYPE artist AS (
  artist_name TEXT,
  foo_artist_id TEXT,
  artist_image_url TEXT,
  artist_rank int
);

我正在尝试查询“foo_user_id”等于我传递给函数的所有行。这是示例代码。

func GetHistoricalTopArtists(foo_user_id string) ([]TopArtists, error) {
// connect to DB etc..

// create prepared statement
stmtStr := `SELECT * FROM artists WHERE foo_user_id=$1`

// error check...

// iterate through all rows to get an array of []TopArtists
defer rows.Close()
    for rows.Next() {
        topArtist := new(TopArtists)
        err := rows.Scan(&topArtist.Id, &topArtist.FooUserId, &topArtist.CreatedAt, &topArtist.TimeSpan, &topArtist.Artists)
        if err != nil {
            log.Fatalf("Something went wrong %v", err)
        }
        topArtists = append(topArtists, *topArtist)
    }
}

为了在 Go 中表示这些数据,我创建了以下结构

// Represents a row
type TopArtists struct {
    Id        int64    `json:"id" db:"id"`
    FooUserId string   `json:"foo_user_id" db:"foo_user_id"`
    CreatedAt string   `json:"created_at" db:"created_at"`
    TimeSpan  string   `json:"time_span" db:"time_span"`
    Artists   []Artist `json:"items" db:"items"`
}

// Represents the artist column
type Artist struct {
    ArtistName      string `json:"artist_name"`
    ArtistId        string `json:"foo_artist_id"`
    ArtistImageURL  string `json:"artist_image_url"`
    ArtistRank      int    `json:"artist_rank"`
}

当我调用执行查询的函数时(我在上面描述的那个)。我收到以下错误。

列索引 4 上的扫描错误,名称“items”:不支持扫描,将 driver.Value 类型 []uint8 存储到类型 *[]database.Artist。

我有一个 Value() 函数,但我不确定如何为我制作的自定义结构的数组实现 Scan() 函数。

这是我的 Value() 函数,我尝试阅读有关扫描原始类型(字符串、int 等)数组的文档和类似帖子,但我无法将逻辑应用于自定义 PostgreSQL 类型。

func (a Artist) Value() (driver.Value, error) {
    s := fmt.Sprintf("(%s, %s, %s, %d)",
        a.ArtistName,
        a.FooArtistId,
        a.ArtistImageURL,
        a.ArtistRank)
    return []byte(s), nil
}

【问题讨论】:

  • 请注意,您要存储/读取的字段类型是[]Artist,因此在Artist 上声明Value/Scan 方法对您没有任何帮助。您需要声明一个切片类型,例如type ArtistSlice []Artist,使用 that 作为字段的类型,并在 that 上实现 Value/Scan 方法。

标签: go


【解决方案1】:

@mkopriva - ...您需要声明一个切片类型,例如type ArtistSlice []Artist, 使用它作为字段的类型,并实现 Value/Scan 方法 那个

Created custom Composite Types Artist 在 Postgresq 中有一个严格的结构为

{(david,38,url,1),(david2,2,"url 2",2)}

那么你必须使用自定义marshal/unmarshal算法实现Value/Scan方法

例如

type Artists []Artist

func (a *Artists) Scan(value interface{}) error {
    source, ok := value.(string) // input example ?? {"(david,38,url,1)","(david2,2,\"url 2\",2)"}
    if !ok {
        return errors.New("incompatible type")
    }
    
    var res Artists
    artists := strings.Split(source, "\",\"")
    for _, artist := range artists {
        for _, old := range []string{"\\\"","\"","{", "}","(",")"} {
            artist = strings.ReplaceAll(artist, old, "")
        }
        artistRawData := strings.Split(artist, ",")
        i, err := strconv.Atoi(artistRawData[1])
        if err != nil {
            return fmt.Errorf("parce ArtistRank raw data (%s) in %d iteration error: %v", artist, i, err)
        }
        res = append(res, Artist{
            ArtistName:     artistRawData[0],
            ArtistId:       artistRawData[1],
            ArtistImageURL: artistRawData[2],
            ArtistRank:     i,
        })
    }
    *a = res
    return nil
}

【讨论】:

  • 这非常有效。我遇到的唯一问题是我必须修改源,ok := value.(string) 为字节,ok := value.([]byte) 因为它没有正确地将其转换为字符串。然后我必须通过 source := string(bytes) 将字节数组转换为字符串。以防万一有人遇到和我一样的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-08-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-16
  • 2016-07-17
  • 1970-01-01
相关资源
最近更新 更多