Last active
January 26, 2024 07:12
-
-
Save alexisvisco/158d368b16faf2864f0485929a4cd38a to your computer and use it in GitHub Desktop.
a gorm v2 hstore implementation that just works ...
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package hstore | |
import ( | |
"context" | |
"database/sql" | |
"database/sql/driver" | |
"encoding/json" | |
"gorm.io/gorm" | |
"gorm.io/gorm/clause" | |
"gorm.io/gorm/schema" | |
"strings" | |
) | |
// Hstore is a wrapper for transferring Hstore values back and forth easily. | |
type Hstore map[string]sql.NullString | |
func (h Hstore) ToMap() map[string]string { | |
m := make(map[string]string) | |
for k, v := range h { | |
if v.Valid { | |
m[k] = v.String | |
} | |
} | |
return m | |
} | |
func (h Hstore) MarshalJSON() ([]byte, error) { | |
return json.Marshal(h.ToMap()) | |
} | |
// escapes and quotes hstore keys/values | |
// s should be a sql.NullString or string | |
func hQuote(s interface{}) string { | |
var str string | |
switch v := s.(type) { | |
case sql.NullString: | |
if !v.Valid { | |
return "NULL" | |
} | |
str = v.String | |
case string: | |
str = v | |
default: | |
panic("not a string or sql.NullString") | |
} | |
str = strings.Replace(str, "\\", "\\\\", -1) | |
return `"` + strings.Replace(str, "\"", "\\\"", -1) + `"` | |
} | |
// Scan implements the Scanner interface. | |
// | |
// Note h.Map is reallocated before the scan to clear existing values. If the | |
// hstore column's database value is NULL, then h.Map is set to nil instead. | |
func (h *Hstore) Scan(value interface{}) error { | |
if value == nil { | |
h = nil | |
return nil | |
} | |
*h = make(map[string]sql.NullString) | |
var b byte | |
pair := [][]byte{{}, {}} | |
pi := 0 | |
inQuote := false | |
didQuote := false | |
sawSlash := false | |
bindex := 0 | |
for bindex, b = range []byte(value.(string)) { | |
if sawSlash { | |
pair[pi] = append(pair[pi], b) | |
sawSlash = false | |
continue | |
} | |
switch b { | |
case '\\': | |
sawSlash = true | |
continue | |
case '"': | |
inQuote = !inQuote | |
if !didQuote { | |
didQuote = true | |
} | |
continue | |
default: | |
if !inQuote { | |
switch b { | |
case ' ', '\t', '\n', '\r': | |
continue | |
case '=': | |
continue | |
case '>': | |
pi = 1 | |
didQuote = false | |
continue | |
case ',': | |
s := string(pair[1]) | |
if !didQuote && len(s) == 4 && strings.ToLower(s) == "null" { | |
(*h)[string(pair[0])] = sql.NullString{String: "", Valid: false} | |
} else { | |
(*h)[string(pair[0])] = sql.NullString{String: string(pair[1]), Valid: true} | |
} | |
pair[0] = []byte{} | |
pair[1] = []byte{} | |
pi = 0 | |
continue | |
} | |
} | |
} | |
pair[pi] = append(pair[pi], b) | |
} | |
if bindex > 0 { | |
s := string(pair[1]) | |
if !didQuote && len(s) == 4 && strings.ToLower(s) == "null" { | |
(*h)[string(pair[0])] = sql.NullString{String: "", Valid: false} | |
} else { | |
(*h)[string(pair[0])] = sql.NullString{String: string(pair[1]), Valid: true} | |
} | |
} | |
return nil | |
} | |
// Value implements the driver Valuer interface. Note if h.Map is nil, the | |
// database column value will be set to NULL. | |
func (h Hstore) Value() (driver.Value, error) { | |
if h == nil { | |
return nil, nil | |
} | |
parts := []string{} | |
for key, val := range h { | |
thispart := hQuote(key) + "=>" + hQuote(val) | |
parts = append(parts, thispart) | |
} | |
return []byte(strings.Join(parts, ",")), nil | |
} | |
// GormDataType gorm common data type | |
func (h Hstore) GormDataType() string { | |
return "hstore" | |
} | |
// GormDBDataType gorm db data type | |
func (h Hstore) GormDBDataType(db *gorm.DB, field *schema.Field) string { | |
switch db.Dialector.Name() { | |
case "postgres": | |
return "HSTORE" | |
} | |
return "" | |
} | |
func (h Hstore) GormValue(_ context.Context, db *gorm.DB) clause.Expr { | |
if len(h) == 0 { | |
return gorm.Expr("NULL") | |
} | |
data, _ := h.Value() | |
return gorm.Expr("?", string(data.([]byte))) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment