243 lines
5.5 KiB
Go
243 lines
5.5 KiB
Go
|
package msgp
|
||
|
|
||
|
import (
|
||
|
"math"
|
||
|
)
|
||
|
|
||
|
// Locate returns a []byte pointing to the field
|
||
|
// in a messagepack map with the provided key. (The returned []byte
|
||
|
// points to a sub-slice of 'raw'; Locate does no allocations.) If the
|
||
|
// key doesn't exist in the map, a zero-length []byte will be returned.
|
||
|
func Locate(key string, raw []byte) []byte {
|
||
|
s, n := locate(raw, key)
|
||
|
return raw[s:n]
|
||
|
}
|
||
|
|
||
|
// Replace takes a key ("key") in a messagepack map ("raw")
|
||
|
// and replaces its value with the one provided and returns
|
||
|
// the new []byte. The returned []byte may point to the same
|
||
|
// memory as "raw". Replace makes no effort to evaluate the validity
|
||
|
// of the contents of 'val'. It may use up to the full capacity of 'raw.'
|
||
|
// Replace returns 'nil' if the field doesn't exist or if the object in 'raw'
|
||
|
// is not a map.
|
||
|
func Replace(key string, raw []byte, val []byte) []byte {
|
||
|
start, end := locate(raw, key)
|
||
|
if start == end {
|
||
|
return nil
|
||
|
}
|
||
|
return replace(raw, start, end, val, true)
|
||
|
}
|
||
|
|
||
|
// CopyReplace works similarly to Replace except that the returned
|
||
|
// byte slice does not point to the same memory as 'raw'. CopyReplace
|
||
|
// returns 'nil' if the field doesn't exist or 'raw' isn't a map.
|
||
|
func CopyReplace(key string, raw []byte, val []byte) []byte {
|
||
|
start, end := locate(raw, key)
|
||
|
if start == end {
|
||
|
return nil
|
||
|
}
|
||
|
return replace(raw, start, end, val, false)
|
||
|
}
|
||
|
|
||
|
// Remove removes a key-value pair from 'raw'. It returns
|
||
|
// 'raw' unchanged if the key didn't exist.
|
||
|
func Remove(key string, raw []byte) []byte {
|
||
|
start, end := locateKV(raw, key)
|
||
|
if start == end {
|
||
|
return raw
|
||
|
}
|
||
|
raw = raw[:start+copy(raw[start:], raw[end:])]
|
||
|
return resizeMap(raw, -1)
|
||
|
}
|
||
|
|
||
|
// HasKey returns whether the map in 'raw' has
|
||
|
// a field with key 'key'
|
||
|
func HasKey(key string, raw []byte) bool {
|
||
|
sz, bts, err := ReadMapHeaderBytes(raw)
|
||
|
if err != nil {
|
||
|
return false
|
||
|
}
|
||
|
var field []byte
|
||
|
for i := uint32(0); i < sz; i++ {
|
||
|
field, bts, err = ReadStringZC(bts)
|
||
|
if err != nil {
|
||
|
return false
|
||
|
}
|
||
|
if UnsafeString(field) == key {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func replace(raw []byte, start int, end int, val []byte, inplace bool) []byte {
|
||
|
ll := end - start // length of segment to replace
|
||
|
lv := len(val)
|
||
|
|
||
|
if inplace {
|
||
|
extra := lv - ll
|
||
|
|
||
|
// fastest case: we're doing
|
||
|
// a 1:1 replacement
|
||
|
if extra == 0 {
|
||
|
copy(raw[start:], val)
|
||
|
return raw
|
||
|
|
||
|
} else if extra < 0 {
|
||
|
// 'val' smaller than replaced value
|
||
|
// copy in place and shift back
|
||
|
|
||
|
x := copy(raw[start:], val)
|
||
|
y := copy(raw[start+x:], raw[end:])
|
||
|
return raw[:start+x+y]
|
||
|
|
||
|
} else if extra < cap(raw)-len(raw) {
|
||
|
// 'val' less than (cap-len) extra bytes
|
||
|
// copy in place and shift forward
|
||
|
raw = raw[0 : len(raw)+extra]
|
||
|
// shift end forward
|
||
|
copy(raw[end+extra:], raw[end:])
|
||
|
copy(raw[start:], val)
|
||
|
return raw
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// we have to allocate new space
|
||
|
out := make([]byte, len(raw)+len(val)-ll)
|
||
|
x := copy(out, raw[:start])
|
||
|
y := copy(out[x:], val)
|
||
|
copy(out[x+y:], raw[end:])
|
||
|
return out
|
||
|
}
|
||
|
|
||
|
// locate does a naive O(n) search for the map key; returns start, end
|
||
|
// (returns 0,0 on error)
|
||
|
func locate(raw []byte, key string) (start int, end int) {
|
||
|
var (
|
||
|
sz uint32
|
||
|
bts []byte
|
||
|
field []byte
|
||
|
err error
|
||
|
)
|
||
|
sz, bts, err = ReadMapHeaderBytes(raw)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// loop and locate field
|
||
|
for i := uint32(0); i < sz; i++ {
|
||
|
field, bts, err = ReadStringZC(bts)
|
||
|
if err != nil {
|
||
|
return 0, 0
|
||
|
}
|
||
|
if UnsafeString(field) == key {
|
||
|
// start location
|
||
|
l := len(raw)
|
||
|
start = l - len(bts)
|
||
|
bts, err = Skip(bts)
|
||
|
if err != nil {
|
||
|
return 0, 0
|
||
|
}
|
||
|
end = l - len(bts)
|
||
|
return
|
||
|
}
|
||
|
bts, err = Skip(bts)
|
||
|
if err != nil {
|
||
|
return 0, 0
|
||
|
}
|
||
|
}
|
||
|
return 0, 0
|
||
|
}
|
||
|
|
||
|
// locate key AND value
|
||
|
func locateKV(raw []byte, key string) (start int, end int) {
|
||
|
var (
|
||
|
sz uint32
|
||
|
bts []byte
|
||
|
field []byte
|
||
|
err error
|
||
|
)
|
||
|
sz, bts, err = ReadMapHeaderBytes(raw)
|
||
|
if err != nil {
|
||
|
return 0, 0
|
||
|
}
|
||
|
|
||
|
for i := uint32(0); i < sz; i++ {
|
||
|
tmp := len(bts)
|
||
|
field, bts, err = ReadStringZC(bts)
|
||
|
if err != nil {
|
||
|
return 0, 0
|
||
|
}
|
||
|
if UnsafeString(field) == key {
|
||
|
start = len(raw) - tmp
|
||
|
bts, err = Skip(bts)
|
||
|
if err != nil {
|
||
|
return 0, 0
|
||
|
}
|
||
|
end = len(raw) - len(bts)
|
||
|
return
|
||
|
}
|
||
|
bts, err = Skip(bts)
|
||
|
if err != nil {
|
||
|
return 0, 0
|
||
|
}
|
||
|
}
|
||
|
return 0, 0
|
||
|
}
|
||
|
|
||
|
// delta is delta on map size
|
||
|
func resizeMap(raw []byte, delta int64) []byte {
|
||
|
var sz int64
|
||
|
switch raw[0] {
|
||
|
case mmap16:
|
||
|
sz = int64(big.Uint16(raw[1:]))
|
||
|
if sz+delta <= math.MaxUint16 {
|
||
|
big.PutUint16(raw[1:], uint16(sz+delta))
|
||
|
return raw
|
||
|
}
|
||
|
if cap(raw)-len(raw) >= 2 {
|
||
|
raw = raw[0 : len(raw)+2]
|
||
|
copy(raw[5:], raw[3:])
|
||
|
raw[0] = mmap32
|
||
|
big.PutUint32(raw[1:], uint32(sz+delta))
|
||
|
return raw
|
||
|
}
|
||
|
n := make([]byte, 0, len(raw)+5)
|
||
|
n = AppendMapHeader(n, uint32(sz+delta))
|
||
|
return append(n, raw[3:]...)
|
||
|
|
||
|
case mmap32:
|
||
|
sz = int64(big.Uint32(raw[1:]))
|
||
|
big.PutUint32(raw[1:], uint32(sz+delta))
|
||
|
return raw
|
||
|
|
||
|
default:
|
||
|
sz = int64(rfixmap(raw[0]))
|
||
|
if sz+delta < 16 {
|
||
|
raw[0] = wfixmap(uint8(sz + delta))
|
||
|
return raw
|
||
|
} else if sz+delta <= math.MaxUint16 {
|
||
|
if cap(raw)-len(raw) >= 2 {
|
||
|
raw = raw[0 : len(raw)+2]
|
||
|
copy(raw[3:], raw[1:])
|
||
|
raw[0] = mmap16
|
||
|
big.PutUint16(raw[1:], uint16(sz+delta))
|
||
|
return raw
|
||
|
}
|
||
|
n := make([]byte, 0, len(raw)+5)
|
||
|
n = AppendMapHeader(n, uint32(sz+delta))
|
||
|
return append(n, raw[1:]...)
|
||
|
}
|
||
|
if cap(raw)-len(raw) >= 4 {
|
||
|
raw = raw[0 : len(raw)+4]
|
||
|
copy(raw[5:], raw[1:])
|
||
|
raw[0] = mmap32
|
||
|
big.PutUint32(raw[1:], uint32(sz+delta))
|
||
|
return raw
|
||
|
}
|
||
|
n := make([]byte, 0, len(raw)+5)
|
||
|
n = AppendMapHeader(n, uint32(sz+delta))
|
||
|
return append(n, raw[1:]...)
|
||
|
}
|
||
|
}
|