将Postgresql数组直接读入Golang切片

17

我有一个查询,返回一行数据,其中包含一个字符串数组(character varying[])的单列:

{http://wp.me/p62MJv-Jc,http://tyrant.click/1LGBoD6}

有没有一种简单的方法将其直接读入到 Golang 切片中?例如:

var arr []string

for rows.Next() {
    rows.Scan(&arr)
    fmt.Println(len(arr))
}

结果:

0

1
你正在使用哪个 PostgreSQL 数据库驱动程序?您是否阅读了它的文档(这样的功能将由驱动程序提供)?如果失败,您可能只需创建一个实现 sql.Scanner 的切片类型。 - Dave C
使用 http://godoc.org/github.com/lib/pq 好的,谢谢,我会研究一下的。有点令人失望,这不是开箱即用的。 - p_mcp
顺便提一句,你不应该在没有检查Scan的错误返回之前就查看len(arr)(可能会出现这样的消息:“reflect.Set:类型为[]uint8的值无法分配到类型为[]string”,即数据库驱动程序将列作为[]byte处理)。 - Dave C
1
快速搜索该软件包的问题可以找到:支持Postgresql数组类型#49数组支持#327。您可以在其中之一上发表评论或跟踪。 - Dave C
请查看此链接 https://www.opsdash.com/blog/postgres-arrays-golang.html,按照这个教程,我成功地解决了切片(go)和数组(postgres)之间的映射问题。 - Victor
4个回答

17

我认为这应该可以完成任务。在SQL中使用array_to_json,然后将JSON字符串反序列化为Golang切片。

sql-> select array_to_json(arr) from ....

var arrStr string
var arr []string

for rows.Next() {
    rows.Scan(&arrStr)
    json.Unmarshal([]byte(arrStr), &arr)
    fmt.Println(len(arr))
}

15

如原帖中维克多在评论中提到的,这篇文章通过解释pq.Array()很好地回答了这个问题。

以下内容直接摘自链接:

要将Postgres数组值读入Go切片,请使用:

func getTags(db *sql.DB, title string) (tags []string) {
    // the select query, returning 1 column of array type
    sel := "SELECT tags FROM posts WHERE title=$1"

    // wrap the output parameter in pq.Array for receiving into it
    if err := db.QueryRow(sel, title).Scan(pq.Array(&tags)); err != nil {
        log.Fatal(err)
    }

    return
}

我刚刚在自己的项目中测试过,可以证实它有效。


3
请注意,pq.Array() 只能用于 []bool[]float64[]int64[]string。可以通过查看代码源来轻松确认这一点。 - Richard
1
使用 pq.Array() 会有任何副作用吗?例如性能影响? - chinmayan

2

目前,使用lib/pq库没有直接的方法将PostgreSQL数组加载到Go切片中。也许在某个时候会有这样的方法,但是是否应该由库本身透明处理这种情况还存在一些争议。

然而,一种选择是将结果加载到一个字符串中(看起来像{item1,item2,"comma,item"}),然后使用正则表达式将该字符串拆分成一个字符串切片,如下面的代码所示(部分取自Andrew Harris的此Gist):

import (
    "regexp"
    "strings"
)

var (
    // unquoted array values must not contain: (" , \ { } whitespace NULL)
    // and must be at least one char
    unquotedChar  = `[^",\\{}\s(NULL)]`
    unquotedValue = fmt.Sprintf("(%s)+", unquotedChar)

    // quoted array values are surrounded by double quotes, can be any
    // character except " or \, which must be backslash escaped:
    quotedChar  = `[^"\\]|\\"|\\\\`
    quotedValue = fmt.Sprintf("\"(%s)*\"", quotedChar)

    // an array value may be either quoted or unquoted:
    arrayValue = fmt.Sprintf("(?P<value>(%s|%s))", unquotedValue, quotedValue)

    // Array values are separated with a comma IF there is more than one value:
    arrayExp = regexp.MustCompile(fmt.Sprintf("((%s)(,)?)", arrayValue))
)

// Parse the output string from the array type.
// Regex used: (((?P<value>(([^",\\{}\s(NULL)])+|"([^"\\]|\\"|\\\\)*")))(,)?)
func pgArrayToSlice(array string) []string {
    var valueIndex int
    results := make([]string, 0)
    matches := arrayExp.FindAllStringSubmatch(array, -1)
    for _, match := range matches {
        s := match[valueIndex]
        // the string _might_ be wrapped in quotes, so trim them:
        s = strings.Trim(s, "\"")
        results = append(results, s)
    }
    return results
}

以下是如何使用它的示例:

rows, err := db.Query("SELECT link FROM links")
if err != nil {
    panic(err)
}
var tmp string
for rows.Next() {
    rows.Scan(&tmp)
    links := pgArrayToSlice(tmp)
    fmt.Println(len(links), links)
}

以下是数据库中的内容:

# \d links
    Table "public.links"
 Column |  Type  | Modifiers 
--------+--------+-----------
 link   | text[] | 

# select * from links;
             link             
------------------------------
 {this,that}
 {another,thing}

 {}
 {"test,123","one,two,three"}
(5 rows)

这是上述Go代码的输出结果:
2 []string{"this,", "that"}
2 []string{"another,", "thing"}
2 []string{"another,", "thing"}
0 []string{}
2 []string{"test,123\",", "one,two,three"}

2

我在很多地方看到过这段代码的变体,但是对于某些测试集,它对我来说不起作用。

这里有一些我编写的代码,它可以处理我投入的所有测试值(测试用例如下所示)。它还快了大约80%。

func ParsePGArray(array string) ([]string, error) {
  var out []string
  var arrayOpened,quoteOpened,escapeOpened bool
  item := &bytes.Buffer{}
  for _, r := range array {
    switch {
    case !arrayOpened:
      if r != '{' {
        return nil, errors.New("Doesn't appear to be a postgres array.  Doesn't start with an opening curly brace.")
      }
      arrayOpened = true
    case escapeOpened:
      item.WriteRune(r)
      escapeOpened = false
    case quoteOpened:
      switch r {
      case '\\':
        escapeOpened = true
      case '"':
        quoteOpened = false
        if item.String() == "NULL" {
          item.Reset()
        }
      default:
        item.WriteRune(r)
      }
    case r == '}':
      // done
      out = append(out, item.String())
      return out, nil
    case r == '"':
      quoteOpened = true
    case r == ',':
      // end of item
      out = append(out, item.String())
      item.Reset()
    default:
      item.WriteRune(r)
    }
  }
  return nil, errors.New("Doesn't appear to be a postgres array.  Premature end of string.")
}

以下是测试用例:
scanTests := []struct {
  in   string
  out  []string
}{
  {"{one,two}", []string{"one", "two"}},
  {`{"one, sdf",two}`, []string{"one, sdf", "two"}},
  {`{"\"one\"",two}`, []string{`"one"`, "two"}},
  {`{"\\one\\",two}`, []string{`\one\`, "two"}},
  {`{"{one}",two}`, []string{`{one}`, "two"}},
  {`{"one two"}`, []string{`one two`}},
  {`{"one,two"}`, []string{`one,two`}},
  {`{abcdef:83bf98cc-fec9-4e77-b4cf-99f9fb6655fa-0NH:zxcvzxc:wers:vxdfw-asdf-asdf}`, []string{"abcdef:83bf98cc-fec9-4e77-b4cf-99f9fb6655fa-0NH:zxcvzxc:wers:vxdfw-asdf-asdf"}},
  {`{"",two}`, []string{"","two"}},
  {`{" ","NULL"}`, []string{" ",""}},
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接