response.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. package httpserver
  2. import (
  3. "encoding/hex"
  4. "log"
  5. "net/http"
  6. "github.com/goccy/go-json"
  7. )
  8. // sanitizeRows converts non-JSON scalar values into a lossless textual form.
  9. // BLOBs are rendered as PostgreSQL-style \x-hex so HTTP clients receive the
  10. // same bytea text representation as the PostgreSQL wire protocol instead of an
  11. // encoding-dependent base64 string.
  12. func sanitizeRows(rows [][]interface{}) [][]interface{} {
  13. for _, row := range rows {
  14. for i, v := range row {
  15. if b, ok := v.([]byte); ok {
  16. row[i] = `\x` + hex.EncodeToString(b)
  17. }
  18. }
  19. }
  20. return rows
  21. }
  22. // ColumnInfo represents column metadata.
  23. type ColumnInfo struct {
  24. Name string `json:"name"`
  25. Type string `json:"type"`
  26. }
  27. // QueryResponse represents a query response.
  28. type QueryResponse struct {
  29. Columns []ColumnInfo `json:"columns"`
  30. Rows [][]interface{} `json:"rows"`
  31. RowsAffected int64 `json:"rowsAffected"`
  32. LastInsertID int64 `json:"lastInsertId"`
  33. LSN uint64 `json:"lsn,omitempty"` // commit LSN for COMMIT
  34. QueryPlan []string `json:"queryPlan,omitempty"`
  35. // Metrics for usage tracking and billing
  36. BytesRead int64 `json:"bytesRead"` // Total bytes in the result set
  37. RowsReturned int `json:"rowsReturned"` // Number of rows returned
  38. ExecutionTimeMicro int64 `json:"executionTimeMicro"` // Execution time in microseconds
  39. }
  40. // ExecuteResult represents a single execution result.
  41. type ExecuteResult struct {
  42. RowsAffected int64 `json:"rowsAffected"`
  43. LastInsertID int64 `json:"lastInsertId"`
  44. LSN uint64 `json:"lsn,omitempty"`
  45. }
  46. // ExecuteResponse represents a batch execution response.
  47. type ExecuteResponse struct {
  48. Results []ExecuteResult `json:"results"`
  49. ExecutionTime string `json:"executionTime"`
  50. }
  51. // ErrorResponse represents an error response.
  52. type ErrorResponse struct {
  53. Error ErrorDetail `json:"error"`
  54. }
  55. // ErrorDetail contains error details.
  56. type ErrorDetail struct {
  57. Code string `json:"code"`
  58. Message string `json:"message"`
  59. Details map[string]interface{} `json:"details,omitempty"`
  60. }
  61. // HTTPError represents an HTTP error with custom fields.
  62. type HTTPError struct {
  63. Code string
  64. Message string
  65. Status int
  66. Details map[string]interface{}
  67. }
  68. func (e *HTTPError) Error() string {
  69. return e.Message
  70. }
  71. // writeJSON writes a JSON response.
  72. func writeJSON(w http.ResponseWriter, status int, data interface{}, pretty bool) {
  73. w.Header().Set("Content-Type", "application/json")
  74. w.WriteHeader(status)
  75. encoder := json.NewEncoder(w)
  76. if pretty {
  77. encoder.SetIndent("", " ")
  78. }
  79. encoder.Encode(data)
  80. }
  81. // writeError writes an error response.
  82. func writeError(w http.ResponseWriter, status int, code, message string, details map[string]interface{}) {
  83. resp := ErrorResponse{
  84. Error: ErrorDetail{
  85. Code: code,
  86. Message: message,
  87. Details: details,
  88. },
  89. }
  90. // Log server errors (5xx status codes)
  91. if status >= 500 {
  92. log.Printf("ERROR [%d] %s: %s", status, code, message)
  93. }
  94. writeJSON(w, status, resp, false)
  95. }