| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697 |
- package pgserver
- import (
- "errors"
- "fmt"
- )
- // resultFormatError is a Bind result-format rejection with the SQLSTATE the
- // client must receive.
- type resultFormatError struct {
- code string
- message string
- }
- func (e *resultFormatError) Error() string { return e.message }
- // resolveResultFormats expands a portal's Bind result format list to one format
- // code per result column and rejects binary requests the wire layer cannot
- // encode.
- //
- // The PostgreSQL Bind rules are: an empty list means text for every column, a
- // single entry applies to every column, and any other list length must equal the
- // number of result columns. The result that is resolved here is the one this
- // server emits at Execute; column metadata only exists then because Describe
- // returns NoData. Formats are stored per portal, so two portals bound to the
- // same statement can use different formats.
- //
- // An all-text result returns nil, which keeps the text row path allocation free.
- func (c *Connection) resolveResultFormats(bindFormats []int16, columnTypes []string, columnCount int) ([]int16, error) {
- if len(bindFormats) == 0 {
- return nil, nil
- }
- if len(bindFormats) > 1 && len(bindFormats) != columnCount {
- return nil, &resultFormatError{
- code: ErrCodeProtocolViolation,
- message: fmt.Sprintf("bind message has %d result formats but the query returns %d columns", len(bindFormats), columnCount),
- }
- }
- formats := make([]int16, columnCount)
- if len(bindFormats) == 1 {
- for i := range formats {
- formats[i] = bindFormats[0]
- }
- } else {
- copy(formats, bindFormats)
- }
- allText := true
- for i, format := range formats {
- switch format {
- case 0:
- continue
- case 1:
- allText = false
- default:
- return nil, &resultFormatError{
- code: ErrCodeProtocolViolation,
- message: fmt.Sprintf("invalid Bind result format code %d", format),
- }
- }
- oid := c.getOIDForType(typeNameAt(columnTypes, i))
- if !canEncodeBinaryResult(oid) {
- return nil, &resultFormatError{
- code: ErrCodeFeatureNotSupported,
- message: fmt.Sprintf("binary result format is not supported for column %d (%s, OID %d)", i+1, typeNameAt(columnTypes, i), oid),
- }
- }
- }
- if allText {
- return nil, nil
- }
- return formats, nil
- }
- // failResultFormat reports a result-format error through failExtended so the
- // connection sends an ErrorResponse, discards the rest of the cycle until Sync,
- // and marks an explicit transaction block as failed.
- func (c *Connection) failResultFormat(err error) error {
- return c.failExtended(resultFormatSQLState(err), err)
- }
- // resultFormatSQLState reports the SQLSTATE a result-format rejection carries.
- func resultFormatSQLState(err error) string {
- var formatErr *resultFormatError
- if errors.As(err, &formatErr) {
- return formatErr.code
- }
- return ErrCodeProtocolViolation
- }
- // typeNameAt returns the declared type name of a result column, or "" when the
- // result carries no metadata for it (the wire layer reports "" as TEXT).
- func typeNameAt(columnTypes []string, index int) string {
- if index < 0 || index >= len(columnTypes) {
- return ""
- }
- return columnTypes[index]
- }
|