问题:去插入复合类型数组不支持的类型

sql

CREATE TABLE public.tiantang_page (
    href varchar NOT NULL,
    status int4 NOT NULL,
    description varchar NOT NULL,
    urls url[] NULL
);

CREATE TYPE url AS (
    url varchar,
    status int4);

插入复合类型数组

type url  struct {
    url string
    status int 
}
    var urls [1]url
    urls[0] = url{
        url:    "",
        status: 0,
    }
    update := "UPDATE \"public\".\"tiantang_page\" SET \"urls\"=$1 where \"href\"=$2;"
    r, err := db.Exec(update, pq.Array(urls),href)
    if err != nil {
        log.Fatal(err)
    }

错误

sql:转换参数 $1 类型:不支持的类型 parsetest.url,一个结构

图书馆

https://godoc.org/github.com/lib/pq

解答

请注意,lib/pq不完全支持自定义复合类型。

如果您只想存储 url,那么最简单的方法是在url类型上实现driver.Valuer接口,然后像使用pq.Array一样使用它:

func (u url) Value() (driver.Value, error) {
    return fmt.Sprintf("(%s,%d)", u.url, u.status), nil
}

// ...
r, err := db.Exec(update, pq.Array(urls), href)

更多信息可以在这里找到:https://github.com/lib/pq/issues/544

请注意,我没有尝试使用数组,仅使用切片,因此您可能必须从使用数组切换到使用切片,即使用var urls [1]url代替var urls = make([]url, 1)


如果您还希望能够从数据库中检索 url 数组,那么您必须实现sql.Scanner接口,但是这里pq.Array不是很可靠,您必须在切片类型上实现扫描器并自己进行所有解析。

复合类型的一般格式是(val1, val2, ...)请注意,您必须在包含逗号或括号的值周围加上双引号。例如,要构造一个 url 类型的值,您可以使用文字表达式:(http://example.com,4)。文档中的更多信息。

复合类型的_array_的格式是{"(val1, val2, ...)" [, ...]},请注意,在这种情况下,如果您需要在需要转义的值周围加上双引号。例如{"(http://example.com,4)","(\"http://example.com/?list=foo,bar,baz\",3)"}

因此,您可以看到复合类型中的数据越复杂,解析也就越复杂。

这是一个粗略的示例(不处理引用值):

type urlslice []url

func (s *urlslice) Scan(src interface{}) error {
    var a []byte // the pq array as bytes
    switch v := src.(type) {
    case []byte:
        a = v
    case string:
        a = []byte(v)
    case nil:
        *s = nil
        return nil
    default:
        return fmt.Errorf("urlslice.Scan unexpected src type %T", src)
    }

    a = a[1 : len(a)-1] // drop curly braces
    for i := 0; i < len(a); i++ {
        if a[i] == '"' && (len(a) > (i+1) && a[i+1] == '(') { // element start?
            i += 2 // move past `"(`
            j := i // start of url.url
            u := url{}

            for ; i < len(a) && a[i] != ','; i++ {
            }
            u.url = string(a[j:i])

            i += 1 // move past `,`
            j = i  // start of url.status
            for ; i < len(a) && a[i] != ')'; i++ {
            }
            i64, err := strconv.ParseInt(string(a[j:i]), 10, 64)
            if err != nil {
                return err
            }
            u.status = int(i64)
            *s = append(*s, u)

            i += 2 // move past `)",`
        }
    }
    return nil
}

为了完整起见,这里是切片类型实现的 Valuer 接口,同样没有处理可能需要它的值的正确引用:

func (s urlslice) Value() (driver.Value, error) {
    data := []byte{'{'}
    for _, url := range s {
        data = append(data, '"', '(')
        data = append(data, []byte(url.url)...)
        data = append(data, ',')
        data = strconv.AppendInt(data, int64(url.status), 10)
        data = append(data, ')', '"', ',')
    }
    data[len(data)-1] = '}' // replace last ',' with '}' to close the array
    return data, nil
}

使用urlslice直接实现这两个接口,您可以停止使用pq.Array

var urls = urlslice{{
    url:    "http://example.com",
    status: 4,
}}
update := `UPDATE "public"."tiantang_page" SET "urls"=$1 where "href"=$2`
r, err := db.Exec(update, urls, href)
if err != nil {
    log.Fatal(err)
}

var urls2 urlslice
selurls := `SELECT "urls" FROM "public"."tiantang_page" where "href" = $1`
if err := db.QueryRow(selurls, href).Scan(&urls2); err != nil {
     log.Fatal(err)
}

请记住,以上两个示例都应仅被视为解决此问题的方向的_提示_。这两个示例不仅不完整,因为它们不处理引用值,而且它们也不是非常优雅的实现。

Logo

PostgreSQL社区为您提供最前沿的新闻资讯和知识内容

更多推荐