Go 插入复合类型数组不支持该类型

5

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

你需要告诉其他人你正在使用哪个数据库库。如果它支持PostgreSQL更复杂的数据类型,则可能需要你显式定义映射关系。 - Richard Huxton
@RichardHuxton 谢谢,我已经添加了库的描述。 - 王奕然
你有一个包含多个未导出字段的结构体。那么如何将其序列化为查询呢? - JimB
2个回答

3
请注意,自定义复合类型在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 = make([]url, 1)代替var urls [1]url


如果您还想能够从数据库中检索出urls数组,则必须实现sql.Scanner接口,但是在这里,pq.Array不是很可靠,您必须在切片类型上实现扫描器并自行完成所有解析。

复合类型的一般格式为(val1, val2, ...)请注意,对于包含逗号或括号的值,您必须在其周围加上双引号。例如,要构造url类型的值,您将使用文字表达式:(http://example.com,4)。有关详细信息,请参见文档

数组的格式为{"(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)
}

请记住,上述两个示例只应被视为解决此问题方向的提示。这两个示例不仅在处理引用值时不完整,而且它们的实现也不够优雅。

我并不是一个go语言的用户,而且已经有一年左右的时间没有关注过PostgreSQL驱动程序的状态了。我必须承认,在当时所有选项的不同弱点让我感到失望。特别是当涉及到编写自己的解析器时,go语言似乎非常笨拙。 - Richard Huxton

0

相对完整的复合字面量解析器:

type parseState int     

const ( 
        state_initial     parseState = iota // start
        state_value_start                   // no bytes read from value yet
        state_value                         // unquoted value
        state_quoted                        // inside quote
        state_value_end                     // after a close quote
        state_end                           // after close paren
)

func parseComposite(in []byte) ([]string, error) {
        state := state_initial   
        ret := []string{}
        val := []byte{}      

        for _, b := range in {
                switch state {                       

                case state_initial:               
                        if b != '(' {
                                return nil, fmt.Errorf("initial character not ')': %v", in)
                        } else {
                                state = state_value_start
                        }

                case state_value_start:
                        if b == '"' {
                                state = state_quoted       
                                continue
                        } 
                        fallthrough

                case state_value:
                        if b == ',' {       
                                ret = append(ret, string(val))
                                val = nil
                                state = state_value_start    
                        } else if b == ')' {
                                ret = append(ret, string(val))
                                val = nil
                                state = state_end       
                        } else {                                                                                                                      
                                val = append(val, b) 
                        }

                case state_quoted:
                        if b == '"' {      
                                ret = append(ret, string(val))
                                val = nil
                                state = state_value_end
                        } else {  
                                val = append(val, b)          
                        }   

                case state_value_end:
                        if b == ',' {
                                state = state_value_start
                        } else if b == ')' {    
                                state = state_end
                        } else {          
                                return nil, fmt.Errorf("invalid delimiter after closing quote: %v", in)
                        }

                case state_end:
                        return nil, fmt.Errorf("trailing bytes: %v", in)
                }        
        }                                             

        if state != state_end {                
                return nil, fmt.Errorf("unterminated value: %v", in)
        }                                   

        return ret, nil                
}    

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