如何在Go中为PostgreSQL列编写自定义类型的Scan()函数

huangapple go评论119阅读模式
英文:

How to write Scan() for custom type in PostgreSQL column in Go

问题

所以我有一个表模式,看起来像下面这样

  1. CREATE TABLE artists (
  2. id SERIAL PRIMARY KEY,
  3. foo_user_id TEXT NOT NULL,
  4. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
  5. time_span TIME_SPAN NOT NULL,
  6. items artist [] NOT NULL
  7. );

此外,我还在PostgreSQL中创建了自定义类型,定义如下

  1. CREATE TYPE artist AS (
  2. artist_name TEXT,
  3. foo_artist_id TEXT,
  4. artist_image_url TEXT,
  5. artist_rank int
  6. );

我正在尝试查询所有具有与我传递给函数的"foo_user_id"相等的行。以下是示例代码。

  1. func GetHistoricalTopArtists(foo_user_id string) ([]TopArtists, error) {
  2. // 连接到数据库等..
  3. // 创建预处理语句
  4. stmtStr := `SELECT * FROM artists WHERE foo_user_id=$1`
  5. // 错误检查...
  6. // 遍历所有行以获取[]TopArtists数组
  7. defer rows.Close()
  8. for rows.Next() {
  9. topArtist := new(TopArtists)
  10. err := rows.Scan(&topArtist.Id, &topArtist.FooUserId, &topArtist.CreatedAt, &topArtist.TimeSpan, &topArtist.Artists)
  11. if err != nil {
  12. log.Fatalf("Something went wrong %v", err)
  13. }
  14. topArtists = append(topArtists, *topArtist)
  15. }
  16. }

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

  1. // 表示一行
  2. type TopArtists struct {
  3. Id int64 `json:"id" db:"id"`
  4. FooUserId string `json:"foo_user_id" db:"foo_user_id"`
  5. CreatedAt string `json:"created_at" db:"created_at"`
  6. TimeSpan string `json:"time_span" db:"time_span"`
  7. Artists []Artist `json:"items" db:"items"`
  8. }
  9. // 表示artist列
  10. type Artist struct {
  11. ArtistName string `json:"artist_name"`
  12. ArtistId string `json:"foo_artist_id"`
  13. ArtistImageURL string `json:"artist_image_url"`
  14. ArtistRank int `json:"artist_rank"`
  15. }

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

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

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

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

  1. func (a Artist) Value() (driver.Value, error) {
  2. s := fmt.Sprintf("(%s, %s, %s, %d)",
  3. a.ArtistName,
  4. a.FooArtistId,
  5. a.ArtistImageURL,
  6. a.ArtistRank)
  7. return []byte(s), nil
  8. }
英文:

So I have this table Schema that looks like the following

  1. CREATE TABLE artists (
  2. id SERIAL PRIMARY KEY,
  3. foo_user_id TEXT NOT NULL,
  4. created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
  5. time_span TIME_SPAN NOT NULL,
  6. items artist [] NOT NULL
  7. );

In addition, I also have the custom type created in PostgreSQL that is defined as follows

  1. CREATE TYPE artist AS (
  2. artist_name TEXT,
  3. foo_artist_id TEXT,
  4. artist_image_url TEXT,
  5. artist_rank int
  6. );

I am trying to query all rows that have the "foo_user_id" equal to what I pass into the function. Here is the sample code.

  1. func GetHistoricalTopArtists(foo_user_id string) ([]TopArtists, error) {
  2. // connect to DB etc..
  3. // create prepared statement
  4. stmtStr := `SELECT * FROM artists WHERE foo_user_id=$1`
  5. // error check...
  6. // iterate through all rows to get an array of []TopArtists
  7. defer rows.Close()
  8. for rows.Next() {
  9. topArtist := new(TopArtists)
  10. err := rows.Scan(&topArtist.Id, &topArtist.FooUserId, &topArtist.CreatedAt, &topArtist.TimeSpan, &topArtist.Artists)
  11. if err != nil {
  12. log.Fatalf("Something went wrong %v", err)
  13. }
  14. topArtists = append(topArtists, *topArtist)
  15. }
  16. }

To represent this data in Go I created the following structs

  1. // Represents a row
  2. type TopArtists struct {
  3. Id int64 `json:"id" db:"id"`
  4. FooUserId string `json:"foo_user_id" db:"foo_user_id"`
  5. CreatedAt string `json:"created_at" db:"created_at"`
  6. TimeSpan string `json:"time_span" db:"time_span"`
  7. Artists []Artist `json:"items" db:"items"`
  8. }
  9. // Represents the artist column
  10. type Artist struct {
  11. ArtistName string `json:"artist_name"`
  12. ArtistId string `json:"foo_artist_id"`
  13. ArtistImageURL string `json:"artist_image_url"`
  14. ArtistRank int `json:"artist_rank"`
  15. }

When I call the function that does the query (the one I described above). I get the following error.

Scan error on column index 4, name "items": unsupported Scan, storing driver.Value type []uint8 into type *[]database.Artist.

I have a Value() function, but I am unsure how to implement a Scan() function for the array of the custom struct I have made.

Here is my Value() function, I have attempted to read documentation and similar posts on scanning arrays of primitive types (strings, int, etc) but I could not apply the logic to custom PostgreSQL types.

  1. func (a Artist) Value() (driver.Value, error) {
  2. s := fmt.Sprintf("(%s, %s, %s, %d)",
  3. a.ArtistName,
  4. a.FooArtistId,
  5. a.ArtistImageURL,
  6. a.ArtistRank)
  7. return []byte(s), nil
  8. }

答案1

得分: 1

> @mkopriva - ...你需要声明一个切片类型,例如 type ArtistSlice []Artist,将其作为字段的类型,并在该类型上实现 Value/Scan 方法。

在PostgreSQL中创建了自定义的Composite Types Artist,它有一个严格的结构,如下所示:

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

然后,你需要使用自定义的marshal/unmarshal算法来实现Value/Scan方法。

例如:

  1. type Artists []Artist
  2. func (a *Artists) Scan(value interface{}) error {
  3. source, ok := value.(string) // 输入示例:{(david,38,url,1),(david2,2,"url 2",2)}
  4. if !ok {
  5. return errors.New("不兼容的类型")
  6. }
  7. var res Artists
  8. artists := strings.Split(source, ",")
  9. for _, artist := range artists {
  10. for _, old := range []string{"\\", "\"", "{", "}", "(", ")"} {
  11. artist = strings.ReplaceAll(artist, old, "")
  12. }
  13. artistRawData := strings.Split(artist, ",")
  14. i, err := strconv.Atoi(artistRawData[1])
  15. if err != nil {
  16. return fmt.Errorf("在第%d次迭代中解析ArtistRank原始数据(%s)时出错:%v", i, artist, err)
  17. }
  18. res = append(res, Artist{
  19. ArtistName: artistRawData[0],
  20. ArtistId: artistRawData[1],
  21. ArtistImageURL: artistRawData[2],
  22. ArtistRank: i,
  23. })
  24. }
  25. *a = res
  26. return nil
  27. }
英文:

> @mkopriva - ...You need to declare a slice type, e.g. type ArtistSlice []Artist,
> use that as the field's type, and implement the Value/Scan methods on
> that.

Created custom Composite Types Artist in Postgresq has a strict struct as

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

then you have to implement Value/Scan method with custom marshal/unmarshal algorithm

For example

  1. type Artists []Artist
  2. func (a *Artists) Scan(value interface{}) error {
  3. source, ok := value.(string) // input example 👉🏻 {"(david,38,url,1)","(david2,2,\"url 2\",2)"}
  4. if !ok {
  5. return errors.New("incompatible type")
  6. }
  7. var res Artists
  8. artists := strings.Split(source, "\",\"")
  9. for _, artist := range artists {
  10. for _, old := range []string{"\\\"","\"","{", "}","(",")"} {
  11. artist = strings.ReplaceAll(artist, old, "")
  12. }
  13. artistRawData := strings.Split(artist, ",")
  14. i, err := strconv.Atoi(artistRawData[1])
  15. if err != nil {
  16. return fmt.Errorf("parce ArtistRank raw data (%s) in %d iteration error: %v", artist, i, err)
  17. }
  18. res = append(res, Artist{
  19. ArtistName: artistRawData[0],
  20. ArtistId: artistRawData[1],
  21. ArtistImageURL: artistRawData[2],
  22. ArtistRank: i,
  23. })
  24. }
  25. *a = res
  26. return nil
  27. }

huangapple
  • 本文由 发表于 2021年10月28日 03:10:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/69744248.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定