result_formats.go 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. package pgserver
  2. import (
  3. "errors"
  4. "fmt"
  5. )
  6. // resultFormatError is a Bind result-format rejection with the SQLSTATE the
  7. // client must receive.
  8. type resultFormatError struct {
  9. code string
  10. message string
  11. }
  12. func (e *resultFormatError) Error() string { return e.message }
  13. // resolveResultFormats expands a portal's Bind result format list to one format
  14. // code per result column and rejects binary requests the wire layer cannot
  15. // encode.
  16. //
  17. // The PostgreSQL Bind rules are: an empty list means text for every column, a
  18. // single entry applies to every column, and any other list length must equal the
  19. // number of result columns. The result that is resolved here is the one this
  20. // server emits at Execute; column metadata only exists then because Describe
  21. // returns NoData. Formats are stored per portal, so two portals bound to the
  22. // same statement can use different formats.
  23. //
  24. // An all-text result returns nil, which keeps the text row path allocation free.
  25. func (c *Connection) resolveResultFormats(bindFormats []int16, columnTypes []string, columnCount int) ([]int16, error) {
  26. if len(bindFormats) == 0 {
  27. return nil, nil
  28. }
  29. if len(bindFormats) > 1 && len(bindFormats) != columnCount {
  30. return nil, &resultFormatError{
  31. code: ErrCodeProtocolViolation,
  32. message: fmt.Sprintf("bind message has %d result formats but the query returns %d columns", len(bindFormats), columnCount),
  33. }
  34. }
  35. formats := make([]int16, columnCount)
  36. if len(bindFormats) == 1 {
  37. for i := range formats {
  38. formats[i] = bindFormats[0]
  39. }
  40. } else {
  41. copy(formats, bindFormats)
  42. }
  43. allText := true
  44. for i, format := range formats {
  45. switch format {
  46. case 0:
  47. continue
  48. case 1:
  49. allText = false
  50. default:
  51. return nil, &resultFormatError{
  52. code: ErrCodeProtocolViolation,
  53. message: fmt.Sprintf("invalid Bind result format code %d", format),
  54. }
  55. }
  56. oid := c.getOIDForType(typeNameAt(columnTypes, i))
  57. if !canEncodeBinaryResult(oid) {
  58. return nil, &resultFormatError{
  59. code: ErrCodeFeatureNotSupported,
  60. message: fmt.Sprintf("binary result format is not supported for column %d (%s, OID %d)", i+1, typeNameAt(columnTypes, i), oid),
  61. }
  62. }
  63. }
  64. if allText {
  65. return nil, nil
  66. }
  67. return formats, nil
  68. }
  69. // failResultFormat reports a result-format error through failExtended so the
  70. // connection sends an ErrorResponse, discards the rest of the cycle until Sync,
  71. // and marks an explicit transaction block as failed.
  72. func (c *Connection) failResultFormat(err error) error {
  73. return c.failExtended(resultFormatSQLState(err), err)
  74. }
  75. // resultFormatSQLState reports the SQLSTATE a result-format rejection carries.
  76. func resultFormatSQLState(err error) string {
  77. var formatErr *resultFormatError
  78. if errors.As(err, &formatErr) {
  79. return formatErr.code
  80. }
  81. return ErrCodeProtocolViolation
  82. }
  83. // typeNameAt returns the declared type name of a result column, or "" when the
  84. // result carries no metadata for it (the wire layer reports "" as TEXT).
  85. func typeNameAt(columnTypes []string, index int) string {
  86. if index < 0 || index >= len(columnTypes) {
  87. return ""
  88. }
  89. return columnTypes[index]
  90. }