2
0

result_binary.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554
  1. package pgserver
  2. import (
  3. "encoding/binary"
  4. "errors"
  5. "fmt"
  6. "math"
  7. "strconv"
  8. "strings"
  9. "github.com/danfragoso/pizzasql-next/pkg/executor"
  10. )
  11. // Binary result encoding.
  12. //
  13. // DataRow values are normally sent in the PostgreSQL text format. A client that
  14. // asks for a binary result column (Bind result format code 1) gets the same
  15. // value in PostgreSQL's binary representation for the OID RowDescription
  16. // reports. Values are appended directly into the DataRow buffer, and a value
  17. // that cannot be represented fails the row before it is written.
  18. //
  19. // The supported OIDs are the ones this engine reports (see getOIDForType) plus
  20. // the array element types that appear in binary array headers. Types without a
  21. // binary representation here (timestamptz 1184, date 1082) are rejected at
  22. // execution time with 0A000 rather than being sent as text.
  23. // canEncodeBinaryResult reports whether the wire layer can encode a result
  24. // column of this OID in the binary format.
  25. func canEncodeBinaryResult(oid int32) bool {
  26. switch oid {
  27. case 16, // bool
  28. 17, // bytea
  29. 20, // int8
  30. 21, // int2
  31. 23, // int4
  32. 25, // text
  33. 19, // name
  34. 26, // oid
  35. 700, // float4
  36. 701, // float8
  37. 1043, // varchar
  38. 1700, // numeric
  39. 24, // regproc
  40. 2205, // regclass
  41. 2206, // regtype
  42. 3802: // jsonb
  43. return true
  44. }
  45. _, ok := binaryResultElementOID(oid)
  46. return ok
  47. }
  48. // binaryResultElementOID maps a one-dimensional array OID to the element OID its
  49. // binary envelope must carry.
  50. func binaryResultElementOID(arrayOID int32) (int32, bool) {
  51. switch arrayOID {
  52. case 1000: // bool[]
  53. return 16, true
  54. case 1005: // int2[]
  55. return 21, true
  56. case 1007: // int4[]
  57. return 23, true
  58. case 1009: // text[]
  59. return 25, true
  60. case 1015: // varchar[]
  61. return 1043, true
  62. case 1016: // int8[]
  63. return 20, true
  64. case 1021: // float4[]
  65. return 700, true
  66. case 1022: // float8[]
  67. return 701, true
  68. case 1231: // numeric[]
  69. return 1700, true
  70. default:
  71. return 0, false
  72. }
  73. }
  74. // binaryResultEncodeError reports a value the requested binary format cannot
  75. // represent, such as text left in an INTEGER column by SQLite affinity. The
  76. // connection reports it as invalid text input and sends no further rows.
  77. type binaryResultEncodeError struct {
  78. err error
  79. }
  80. func (e *binaryResultEncodeError) Error() string { return e.err.Error() }
  81. func (e *binaryResultEncodeError) Unwrap() error { return e.err }
  82. func binaryEncodeErrorf(format string, args ...interface{}) error {
  83. return &binaryResultEncodeError{err: fmt.Errorf(format, args...)}
  84. }
  85. // failBinaryEncode reports a value the requested binary format cannot represent.
  86. // Rows already written stay written (an error discards the rest of the result),
  87. // but the failing DataRow is never emitted.
  88. func (c *Connection) failBinaryEncode(err error) error {
  89. var encodeErr *binaryResultEncodeError
  90. if errors.As(err, &encodeErr) {
  91. return c.failExtended(ErrCodeInvalidText, err)
  92. }
  93. return err
  94. }
  95. // appendBinaryResultValue appends the PostgreSQL binary representation of value
  96. // for oid.
  97. func appendBinaryResultValue(dst []byte, oid int32, value interface{}) ([]byte, error) {
  98. if elementOID, ok := binaryResultElementOID(oid); ok {
  99. return appendBinaryArray(dst, elementOID, value)
  100. }
  101. switch oid {
  102. case 16: // bool
  103. flag, err := binaryBool(value)
  104. if err != nil {
  105. return dst, err
  106. }
  107. if flag {
  108. return append(dst, 1), nil
  109. }
  110. return append(dst, 0), nil
  111. case 17: // bytea
  112. return append(dst, binaryBytes(value)...), nil
  113. case 20, 21, 23: // int8, int2, int4
  114. width := 8
  115. switch oid {
  116. case 21:
  117. width = 2
  118. case 23:
  119. width = 4
  120. }
  121. n, err := binaryInt64(value, oid)
  122. if err != nil {
  123. return dst, err
  124. }
  125. switch width {
  126. case 2:
  127. return append(dst, byte(n>>8), byte(n)), nil
  128. case 4:
  129. return append(dst, byte(n>>24), byte(n>>16), byte(n>>8), byte(n)), nil
  130. default:
  131. return appendUint64(dst, uint64(n)), nil
  132. }
  133. case 19: // name
  134. if reg, ok := value.(executor.RegOID); ok {
  135. return append(dst, reg.String()...), nil
  136. }
  137. return append(dst, textValueString(value)...), nil
  138. case 26, 24, 2205, 2206: // oid, regproc, regclass, regtype
  139. n, err := catalogOIDBinaryValue(value)
  140. if err != nil {
  141. return dst, err
  142. }
  143. return append(dst, byte(n>>24), byte(n>>16), byte(n>>8), byte(n)), nil
  144. case 25, 1043: // text, varchar
  145. return append(dst, textValueString(value)...), nil
  146. case 700: // float4
  147. f, err := binaryFloat64(value)
  148. if err != nil {
  149. return dst, err
  150. }
  151. // A value beyond float4's range would otherwise convert to ±Inf and be
  152. // decoded as infinity instead of the stored value. NaN compares false
  153. // here and stays NaN, which float4 can represent.
  154. if math.Abs(f) > math.MaxFloat32 {
  155. return dst, binaryEncodeErrorf("value %v does not fit float4", f)
  156. }
  157. bits := math.Float32bits(float32(f))
  158. return append(dst, byte(bits>>24), byte(bits>>16), byte(bits>>8), byte(bits)), nil
  159. case 701: // float8
  160. f, err := binaryFloat64(value)
  161. if err != nil {
  162. return dst, err
  163. }
  164. return appendUint64(dst, math.Float64bits(f)), nil
  165. case 1700: // numeric
  166. return appendNumericValue(dst, value)
  167. case 3802: // jsonb: version byte plus canonical JSON text
  168. dst = append(dst, 1)
  169. return append(dst, binaryJSONText(value)...), nil
  170. default:
  171. return dst, binaryEncodeErrorf("binary result format is not supported for OID %d", oid)
  172. }
  173. }
  174. // appendBinaryArray appends a one-dimensional binary array. The stored value is
  175. // the canonical PostgreSQL array text, so parsing it yields the same elements
  176. // the text format carries; NULL elements keep their -1 length and an empty
  177. // array has no dimension header, as array_recv expects.
  178. func appendBinaryArray(dst []byte, elementOID int32, value interface{}) ([]byte, error) {
  179. text := binaryJSONText(value)
  180. elements, err := executor.ParseArrayText(text)
  181. if err != nil {
  182. return dst, binaryEncodeErrorf("invalid array value %q: %v", text, err)
  183. }
  184. hasNull := false
  185. for _, element := range elements {
  186. if element == nil {
  187. hasNull = true
  188. break
  189. }
  190. }
  191. dimensions := int32(1)
  192. if len(elements) == 0 {
  193. dimensions = 0
  194. }
  195. dst = appendUint32(dst, uint32(dimensions))
  196. if hasNull {
  197. dst = appendUint32(dst, 1)
  198. } else {
  199. dst = appendUint32(dst, 0)
  200. }
  201. dst = appendUint32(dst, uint32(elementOID))
  202. if dimensions == 0 {
  203. return dst, nil
  204. }
  205. dst = appendUint32(dst, uint32(len(elements)))
  206. dst = appendUint32(dst, 1) // lower bound
  207. for _, element := range elements {
  208. if element == nil {
  209. dst = appendUint32(dst, math.MaxUint32) // -1: NULL element
  210. continue
  211. }
  212. start := len(dst)
  213. dst = appendUint32(dst, 0)
  214. dst, err = appendBinaryResultValue(dst, elementOID, *element)
  215. if err != nil {
  216. return dst, err
  217. }
  218. binary.BigEndian.PutUint32(dst[start:start+4], uint32(len(dst)-start-4))
  219. }
  220. return dst, nil
  221. }
  222. func appendUint32(dst []byte, n uint32) []byte {
  223. return append(dst, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
  224. }
  225. func appendUint64(dst []byte, n uint64) []byte {
  226. return append(dst, byte(n>>56), byte(n>>48), byte(n>>40), byte(n>>32), byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
  227. }
  228. // binaryInt64 converts an engine value to an integer result of the given OID.
  229. // Text is accepted because SQLite affinity can leave a text value in an integer
  230. // column; a value that does not fit the reported OID is an encoding error
  231. // instead of a silently truncated one.
  232. // catalogOIDBinaryValue encodes an oid/regclass/regtype value. A RegOID carries
  233. // its numeric OID separately from its display name.
  234. func catalogOIDBinaryValue(value interface{}) (int64, error) {
  235. if reg, ok := value.(executor.RegOID); ok {
  236. return binaryInt64(reg.OID(), 23)
  237. }
  238. return binaryInt64(value, 23)
  239. }
  240. func binaryInt64(value interface{}, oid int32) (int64, error) {
  241. var n int64
  242. switch v := value.(type) {
  243. case int64:
  244. n = v
  245. case int:
  246. n = int64(v)
  247. case int32:
  248. n = int64(v)
  249. case int16:
  250. n = int64(v)
  251. case int8:
  252. n = int64(v)
  253. case float64:
  254. if math.Trunc(v) != v || v < math.MinInt64 || v >= math.MaxInt64 {
  255. return 0, binaryEncodeErrorf("value %v is not an integer", v)
  256. }
  257. n = int64(v)
  258. case string:
  259. parsed, err := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
  260. if err != nil {
  261. return 0, binaryEncodeErrorf("value %q is not an integer", v)
  262. }
  263. n = parsed
  264. case []byte:
  265. parsed, err := strconv.ParseInt(strings.TrimSpace(string(v)), 10, 64)
  266. if err != nil {
  267. return 0, binaryEncodeErrorf("value %q is not an integer", v)
  268. }
  269. n = parsed
  270. default:
  271. return 0, binaryEncodeErrorf("value of type %T is not an integer", value)
  272. }
  273. switch oid {
  274. case 21:
  275. if n < math.MinInt16 || n > math.MaxInt16 {
  276. return 0, binaryEncodeErrorf("value %d does not fit int2", n)
  277. }
  278. case 23:
  279. if n < math.MinInt32 || n > math.MaxInt32 {
  280. return 0, binaryEncodeErrorf("value %d does not fit int4", n)
  281. }
  282. }
  283. return n, nil
  284. }
  285. // binaryFloat64 converts an engine value to a floating-point result.
  286. func binaryFloat64(value interface{}) (float64, error) {
  287. switch v := value.(type) {
  288. case float64:
  289. return v, nil
  290. case float32:
  291. return float64(v), nil
  292. case int64:
  293. return float64(v), nil
  294. case int:
  295. return float64(v), nil
  296. case int32:
  297. return float64(v), nil
  298. case int16:
  299. return float64(v), nil
  300. case string:
  301. parsed, err := strconv.ParseFloat(strings.TrimSpace(v), 64)
  302. if err != nil {
  303. return 0, binaryEncodeErrorf("value %q is not a floating-point number", v)
  304. }
  305. return parsed, nil
  306. case []byte:
  307. parsed, err := strconv.ParseFloat(strings.TrimSpace(string(v)), 64)
  308. if err != nil {
  309. return 0, binaryEncodeErrorf("value %q is not a floating-point number", v)
  310. }
  311. return parsed, nil
  312. default:
  313. return 0, binaryEncodeErrorf("value of type %T is not a floating-point number", value)
  314. }
  315. }
  316. // binaryBool converts an engine value to a boolean result. Boolean columns hold
  317. // int64 0/1 from storage, bool from expressions, and text when the inserted
  318. // literal did not convert.
  319. func binaryBool(value interface{}) (bool, error) {
  320. switch v := value.(type) {
  321. case bool:
  322. return v, nil
  323. case int64:
  324. return v != 0, nil
  325. case int:
  326. return v != 0, nil
  327. case int32:
  328. return v != 0, nil
  329. case int16:
  330. return v != 0, nil
  331. case float64:
  332. return v != 0, nil
  333. case string:
  334. return boolText(v)
  335. case []byte:
  336. return boolText(string(v))
  337. default:
  338. return false, binaryEncodeErrorf("value of type %T is not a boolean", value)
  339. }
  340. }
  341. func boolText(text string) (bool, error) {
  342. switch strings.ToLower(strings.TrimSpace(text)) {
  343. case "t", "true", "1", "y", "yes", "on":
  344. return true, nil
  345. case "f", "false", "0", "n", "no", "off":
  346. return false, nil
  347. default:
  348. return false, binaryEncodeErrorf("value %q is not a boolean", text)
  349. }
  350. }
  351. // binaryBytes returns the bytes a binary bytea result carries. A non-[]byte
  352. // value uses the same text the bytea text format would send, which is the
  353. // escape-format interpretation of the value's spelling.
  354. func binaryBytes(value interface{}) []byte {
  355. if b, ok := value.([]byte); ok {
  356. return b
  357. }
  358. return []byte(textValueString(value))
  359. }
  360. // binaryJSONText returns the JSON text a jsonb result carries.
  361. func binaryJSONText(value interface{}) string {
  362. switch v := value.(type) {
  363. case string:
  364. return v
  365. case []byte:
  366. return string(v)
  367. default:
  368. return textValueString(value)
  369. }
  370. }
  371. // appendNumericValue appends a PostgreSQL binary numeric value. Numeric values
  372. // are stored as exact decimal text, so they are encoded digit by digit into
  373. // base-10000 groups; converting through float64 would round digits above 2^53
  374. // and lose the display scale (1.10 would become 1.1).
  375. func appendNumericValue(dst []byte, value interface{}) ([]byte, error) {
  376. var text string
  377. switch v := value.(type) {
  378. case string:
  379. text = v
  380. case []byte:
  381. text = string(v)
  382. case int64:
  383. text = strconv.FormatInt(v, 10)
  384. case int:
  385. text = strconv.FormatInt(int64(v), 10)
  386. case float64:
  387. text = strconv.FormatFloat(v, 'g', -1, 64)
  388. default:
  389. return dst, binaryEncodeErrorf("value of type %T is not numeric", value)
  390. }
  391. return appendNumericText(dst, text)
  392. }
  393. // appendNumericText encodes exact decimal text as a PostgreSQL numeric: ndigits,
  394. // weight, sign, dscale, then base-10000 groups. Exponents are applied to the
  395. // decimal point, trailing fraction zeros only affect dscale, and leading zero
  396. // groups are dropped while adjusting weight.
  397. func appendNumericText(dst []byte, text string) ([]byte, error) {
  398. invalid := func() ([]byte, error) {
  399. return dst, binaryEncodeErrorf("invalid numeric value %q", text)
  400. }
  401. s := strings.TrimSpace(text)
  402. if s == "" {
  403. return invalid()
  404. }
  405. negative := false
  406. if s[0] == '+' || s[0] == '-' {
  407. negative = s[0] == '-'
  408. s = s[1:]
  409. }
  410. if s == "" {
  411. return invalid()
  412. }
  413. exponent := 0
  414. if index := strings.IndexAny(s, "eE"); index >= 0 {
  415. parsed, err := strconv.Atoi(s[index+1:])
  416. if err != nil {
  417. return invalid()
  418. }
  419. if parsed > math.MaxInt16*4 || parsed < math.MinInt16*4 {
  420. return dst, binaryEncodeErrorf("numeric exponent %d is out of range", parsed)
  421. }
  422. exponent = parsed
  423. s = s[:index]
  424. }
  425. point := strings.IndexByte(s, '.')
  426. var digits string
  427. if point < 0 {
  428. point = len(s)
  429. digits = s
  430. } else {
  431. digits = s[:point] + s[point+1:]
  432. }
  433. if digits == "" {
  434. return invalid()
  435. }
  436. for i := 0; i < len(digits); i++ {
  437. if digits[i] < '0' || digits[i] > '9' {
  438. return invalid()
  439. }
  440. }
  441. point += exponent
  442. if point > math.MaxInt16*4 || point < math.MinInt16*4 {
  443. return dst, binaryEncodeErrorf("numeric exponent is out of range in %q", text)
  444. }
  445. // The display scale is the number of fraction digits after the exponent,
  446. // including zeros this encoder drops from the base-10000 groups (1.10 keeps
  447. // dscale 2).
  448. dscale := len(digits) - point
  449. if dscale < 0 {
  450. dscale = 0
  451. }
  452. if dscale > math.MaxInt16 {
  453. return dst, binaryEncodeErrorf("numeric scale %d is out of range", dscale)
  454. }
  455. integerDigits := ""
  456. if point >= len(digits) {
  457. integerDigits = digits + strings.Repeat("0", point-len(digits))
  458. } else if point > 0 {
  459. integerDigits = digits[:point]
  460. }
  461. fractionDigits := ""
  462. if point < 0 {
  463. fractionDigits = strings.Repeat("0", -point) + digits
  464. } else if point < len(digits) {
  465. fractionDigits = digits[point:]
  466. }
  467. var groups []uint16
  468. weight := 0
  469. if trimmed := strings.TrimLeft(integerDigits, "0"); trimmed != "" {
  470. padding := (4 - len(trimmed)%4) % 4
  471. padded := strings.Repeat("0", padding) + trimmed
  472. weight = len(padded)/4 - 1
  473. for i := 0; i < len(padded); i += 4 {
  474. groups = append(groups, uint16(decimalGroup(padded[i:i+4])))
  475. }
  476. }
  477. if trimmed := strings.TrimRight(fractionDigits, "0"); trimmed != "" {
  478. padding := (4 - len(trimmed)%4) % 4
  479. padded := trimmed + strings.Repeat("0", padding)
  480. if len(groups) == 0 {
  481. weight = -1
  482. }
  483. for i := 0; i < len(padded); i += 4 {
  484. groups = append(groups, uint16(decimalGroup(padded[i:i+4])))
  485. }
  486. }
  487. leading := 0
  488. for leading < len(groups) && groups[leading] == 0 {
  489. leading++
  490. }
  491. if leading == len(groups) {
  492. groups = nil
  493. weight = 0
  494. } else {
  495. groups = groups[leading:]
  496. weight -= leading
  497. }
  498. for len(groups) > 0 && groups[len(groups)-1] == 0 {
  499. groups = groups[:len(groups)-1]
  500. }
  501. if weight < math.MinInt16 || weight > math.MaxInt16 {
  502. return dst, binaryEncodeErrorf("numeric weight %d is out of range", weight)
  503. }
  504. if len(groups) > math.MaxInt16 {
  505. return dst, binaryEncodeErrorf("numeric has %d base-10000 groups, out of range", len(groups))
  506. }
  507. dst = append(dst, byte(len(groups)>>8), byte(len(groups)))
  508. dst = append(dst, byte(weight>>8), byte(weight))
  509. sign := 0
  510. if negative && len(groups) > 0 {
  511. sign = 0x4000
  512. }
  513. dst = append(dst, byte(sign>>8), byte(sign))
  514. dst = append(dst, byte(dscale>>8), byte(dscale))
  515. for _, group := range groups {
  516. dst = append(dst, byte(group>>8), byte(group))
  517. }
  518. return dst, nil
  519. }
  520. // decimalGroup parses four decimal digits, which are always in 0000-9999.
  521. func decimalGroup(digits string) int {
  522. n := 0
  523. for i := 0; i < len(digits); i++ {
  524. n = n*10 + int(digits[i]-'0')
  525. }
  526. return n
  527. }