hstore.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package hstore
  2. import (
  3. "database/sql"
  4. "database/sql/driver"
  5. "strings"
  6. )
  7. // Hstore is a wrapper for transferring Hstore values back and forth easily.
  8. type Hstore struct {
  9. Map map[string]sql.NullString
  10. }
  11. // escapes and quotes hstore keys/values
  12. // s should be a sql.NullString or string
  13. func hQuote(s interface{}) string {
  14. var str string
  15. switch v := s.(type) {
  16. case sql.NullString:
  17. if !v.Valid {
  18. return "NULL"
  19. }
  20. str = v.String
  21. case string:
  22. str = v
  23. default:
  24. panic("not a string or sql.NullString")
  25. }
  26. str = strings.Replace(str, "\\", "\\\\", -1)
  27. return `"` + strings.Replace(str, "\"", "\\\"", -1) + `"`
  28. }
  29. // Scan implements the Scanner interface.
  30. //
  31. // Note h.Map is reallocated before the scan to clear existing values. If the
  32. // hstore column's database value is NULL, then h.Map is set to nil instead.
  33. func (h *Hstore) Scan(value interface{}) error {
  34. if value == nil {
  35. h.Map = nil
  36. return nil
  37. }
  38. h.Map = make(map[string]sql.NullString)
  39. var b byte
  40. pair := [][]byte{{}, {}}
  41. pi := 0
  42. inQuote := false
  43. didQuote := false
  44. sawSlash := false
  45. bindex := 0
  46. for bindex, b = range value.([]byte) {
  47. if sawSlash {
  48. pair[pi] = append(pair[pi], b)
  49. sawSlash = false
  50. continue
  51. }
  52. switch b {
  53. case '\\':
  54. sawSlash = true
  55. continue
  56. case '"':
  57. inQuote = !inQuote
  58. if !didQuote {
  59. didQuote = true
  60. }
  61. continue
  62. default:
  63. if !inQuote {
  64. switch b {
  65. case ' ', '\t', '\n', '\r':
  66. continue
  67. case '=':
  68. continue
  69. case '>':
  70. pi = 1
  71. didQuote = false
  72. continue
  73. case ',':
  74. s := string(pair[1])
  75. if !didQuote && len(s) == 4 && strings.ToLower(s) == "null" {
  76. h.Map[string(pair[0])] = sql.NullString{String: "", Valid: false}
  77. } else {
  78. h.Map[string(pair[0])] = sql.NullString{String: string(pair[1]), Valid: true}
  79. }
  80. pair[0] = []byte{}
  81. pair[1] = []byte{}
  82. pi = 0
  83. continue
  84. }
  85. }
  86. }
  87. pair[pi] = append(pair[pi], b)
  88. }
  89. if bindex > 0 {
  90. s := string(pair[1])
  91. if !didQuote && len(s) == 4 && strings.ToLower(s) == "null" {
  92. h.Map[string(pair[0])] = sql.NullString{String: "", Valid: false}
  93. } else {
  94. h.Map[string(pair[0])] = sql.NullString{String: string(pair[1]), Valid: true}
  95. }
  96. }
  97. return nil
  98. }
  99. // Value implements the driver Valuer interface. Note if h.Map is nil, the
  100. // database column value will be set to NULL.
  101. func (h Hstore) Value() (driver.Value, error) {
  102. if h.Map == nil {
  103. return nil, nil
  104. }
  105. parts := []string{}
  106. for key, val := range h.Map {
  107. thispart := hQuote(key) + "=>" + hQuote(val)
  108. parts = append(parts, thispart)
  109. }
  110. return []byte(strings.Join(parts, ",")), nil
  111. }