Add insert mutation with bulk insert
This commit is contained in:
39
jsn/README.md
Normal file
39
jsn/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# JSN - Fast low allocation JSON library
|
||||
## Design
|
||||
|
||||
This libary is designed as a set of seperate functions to extract data and mutate
|
||||
JSON. All functions are focused on keeping allocations to a minimum and be as fast
|
||||
as possible. The functions don't validate the JSON a seperate `Validate` function
|
||||
does that.
|
||||
|
||||
The JSON parsing algo processes each object `{}` or array `[]` in a bottom up way
|
||||
where once the end of the array or object is found only then the keys within it are
|
||||
parsed from the top down.
|
||||
|
||||
```
|
||||
{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}], "full_name":"FN1","email":"E1" }
|
||||
|
||||
id: 1
|
||||
|
||||
posts: [{"title":"PT1-1","description":"PD1-1"}]
|
||||
|
||||
[{"title":"PT1-1","description":"PD1-1"}]
|
||||
|
||||
{"title":"PT1-1","description":"PD1-1"}
|
||||
|
||||
title: "PT1-1"
|
||||
|
||||
description: "PD1-1
|
||||
|
||||
full_name: "FN1"
|
||||
|
||||
email: "E1"
|
||||
```
|
||||
|
||||
## Functions
|
||||
|
||||
- Strip: Strip a path from the root to a child node and return the rest
|
||||
- Replace: Replace values by key
|
||||
- Get: Get all keys
|
||||
- Filter: Extract specific keys from an object
|
||||
- Tree: Fetch unique keys from an array or object
|
@ -43,7 +43,7 @@ func Get(b []byte, keys [][]byte) []Field {
|
||||
kmap[xxhash.Sum64(keys[i])] = struct{}{}
|
||||
}
|
||||
|
||||
res := make([]Field, 20)
|
||||
res := make([]Field, 0, 20)
|
||||
|
||||
s, e, d := 0, 0, 0
|
||||
|
||||
@ -127,7 +127,7 @@ func Get(b []byte, keys [][]byte) []Field {
|
||||
_, ok := kmap[xxhash.Sum64(k)]
|
||||
|
||||
if ok {
|
||||
res[n] = Field{k, b[s:(e + 1)]}
|
||||
res = append(res, Field{k, b[s:(e + 1)]})
|
||||
n++
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,10 @@ var (
|
||||
"full_name": "Caroll Orn Sr.",
|
||||
"email": "joannarau@hegmann.io",
|
||||
"__twitter_id": "ABC123"
|
||||
"more": [{
|
||||
"__twitter_id": "more123",
|
||||
"hello: "world
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
@ -163,6 +167,7 @@ func TestGet(t *testing.T) {
|
||||
{[]byte("__twitter_id"), []byte(`"ABCD"`)},
|
||||
{[]byte("__twitter_id"), []byte(`"2048666903444506956"`)},
|
||||
{[]byte("__twitter_id"), []byte(`"ABC123"`)},
|
||||
{[]byte("__twitter_id"), []byte(`"more123"`)},
|
||||
{[]byte("__twitter_id"),
|
||||
[]byte(`[{ "name": "hello" }, { "name": "world"}]`)},
|
||||
{[]byte("__twitter_id"),
|
||||
@ -340,6 +345,80 @@ func TestReplaceEmpty(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeys1(t *testing.T) {
|
||||
json := `[{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]},{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]},{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]}]`
|
||||
|
||||
fields := Keys([]byte(json))
|
||||
|
||||
exp := []string{
|
||||
"id", "posts", "title", "description", "full_name", "email", "books", "name", "description",
|
||||
}
|
||||
|
||||
if len(exp) != len(fields) {
|
||||
t.Errorf("Expected %d fields %d", len(exp), len(fields))
|
||||
}
|
||||
|
||||
for i := range exp {
|
||||
if string(fields[i]) != exp[i] {
|
||||
t.Errorf("Expected field '%s' got '%s'", string(exp[i]), fields[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeys2(t *testing.T) {
|
||||
json := `{"id":1,"posts": [{"title":"PT1-1","description":"PD1-1"}, {"title":"PT1-2","description":"PD1-2"}], "full_name":"FN1","email":"E1","books": [{"name":"BN1-1","description":"BD1-1"},{"name":"BN1-2","description":"BD1-2"},{"name":"BN1-2","description":"BD1-2"}]}`
|
||||
|
||||
fields := Keys([]byte(json))
|
||||
|
||||
exp := []string{
|
||||
"id", "posts", "title", "description", "full_name", "email", "books", "name", "description",
|
||||
}
|
||||
|
||||
// for i := range fields {
|
||||
// fmt.Println("-->", string(fields[i]))
|
||||
// }
|
||||
|
||||
if len(exp) != len(fields) {
|
||||
t.Errorf("Expected %d fields %d", len(exp), len(fields))
|
||||
}
|
||||
|
||||
for i := range exp {
|
||||
if string(fields[i]) != exp[i] {
|
||||
t.Errorf("Expected field '%s' got '%s'", string(exp[i]), fields[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeys3(t *testing.T) {
|
||||
json := `{
|
||||
"insert": {
|
||||
"created_at": "now",
|
||||
"test": { "type1": "a", "type2": "b" },
|
||||
"name": "Hello",
|
||||
"updated_at": "now",
|
||||
"description": "World"
|
||||
},
|
||||
"user": 123
|
||||
}`
|
||||
|
||||
fields := Keys([]byte(json))
|
||||
|
||||
exp := []string{
|
||||
"insert", "created_at", "test", "type1", "type2", "name", "updated_at", "description",
|
||||
"user",
|
||||
}
|
||||
|
||||
if len(exp) != len(fields) {
|
||||
t.Errorf("Expected %d fields %d", len(exp), len(fields))
|
||||
}
|
||||
|
||||
for i := range exp {
|
||||
if string(fields[i]) != exp[i] {
|
||||
t.Errorf("Expected field '%s' got '%s'", string(exp[i]), fields[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkGet(b *testing.B) {
|
||||
b.ReportAllocs()
|
||||
|
||||
|
122
jsn/keys.go
Normal file
122
jsn/keys.go
Normal file
@ -0,0 +1,122 @@
|
||||
package jsn
|
||||
|
||||
func Keys(b []byte) [][]byte {
|
||||
res := make([][]byte, 0, 20)
|
||||
|
||||
s, e, d := 0, 0, 0
|
||||
|
||||
var k []byte
|
||||
state := expectValue
|
||||
|
||||
st := NewStack()
|
||||
ae := 0
|
||||
|
||||
for i := 0; i < len(b); i++ {
|
||||
if state == expectObjClose || state == expectListClose {
|
||||
switch b[i] {
|
||||
case '{', '[':
|
||||
d++
|
||||
case '}', ']':
|
||||
d--
|
||||
}
|
||||
}
|
||||
|
||||
si := st.Peek()
|
||||
|
||||
switch {
|
||||
case state == expectKey && si != nil && i >= si.ss:
|
||||
i = si.se + 1
|
||||
st.Pop()
|
||||
|
||||
case state == expectKey && b[i] == '{':
|
||||
state = expectObjClose
|
||||
s = i
|
||||
d++
|
||||
|
||||
case state == expectObjClose && d == 0 && b[i] == '}':
|
||||
state = expectKey
|
||||
if ae != 0 {
|
||||
st.Push(skipInfo{i, ae})
|
||||
ae = 0
|
||||
}
|
||||
e = i
|
||||
i = s
|
||||
|
||||
case state == expectKey && b[i] == '"':
|
||||
state = expectKeyClose
|
||||
s = i
|
||||
|
||||
case state == expectKeyClose && b[i] == '"':
|
||||
state = expectColon
|
||||
k = b[(s + 1):i]
|
||||
|
||||
case state == expectColon && b[i] == ':':
|
||||
state = expectValue
|
||||
|
||||
case state == expectValue && b[i] == '"':
|
||||
state = expectString
|
||||
s = i
|
||||
|
||||
case state == expectString && b[i] == '"':
|
||||
e = i
|
||||
|
||||
case state == expectValue && b[i] == '{':
|
||||
state = expectObjClose
|
||||
s = i
|
||||
d++
|
||||
|
||||
case state == expectObjClose && d == 0 && b[i] == '}':
|
||||
state = expectKey
|
||||
e = i
|
||||
i = s
|
||||
|
||||
case state == expectValue && b[i] == '[':
|
||||
state = expectListClose
|
||||
s = i
|
||||
d++
|
||||
|
||||
case state == expectListClose && d == 0 && b[i] == ']':
|
||||
state = expectKey
|
||||
ae = i
|
||||
e = i
|
||||
i = s
|
||||
|
||||
case state == expectValue && (b[i] >= '0' && b[i] <= '9'):
|
||||
state = expectNumClose
|
||||
s = i
|
||||
|
||||
case state == expectNumClose &&
|
||||
((b[i] < '0' || b[i] > '9') &&
|
||||
(b[i] != '.' && b[i] != 'e' && b[i] != 'E' && b[i] != '+' && b[i] != '-')):
|
||||
i--
|
||||
e = i
|
||||
|
||||
case state == expectValue &&
|
||||
(b[i] == 'f' || b[i] == 'F' || b[i] == 't' || b[i] == 'T'):
|
||||
state = expectBoolClose
|
||||
s = i
|
||||
|
||||
case state == expectBoolClose && (b[i] == 'e' || b[i] == 'E'):
|
||||
e = i
|
||||
|
||||
case state == expectValue && b[i] == 'n':
|
||||
state = expectNull
|
||||
|
||||
case state == expectNull && b[i] == 'l':
|
||||
e = i
|
||||
}
|
||||
|
||||
if e != 0 {
|
||||
if k != nil {
|
||||
res = append(res, k)
|
||||
}
|
||||
|
||||
state = expectKey
|
||||
k = nil
|
||||
e = 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
51
jsn/stack.go
Normal file
51
jsn/stack.go
Normal file
@ -0,0 +1,51 @@
|
||||
package jsn
|
||||
|
||||
type skipInfo struct {
|
||||
ss, se int
|
||||
}
|
||||
|
||||
type Stack struct {
|
||||
stA [20]skipInfo
|
||||
st []skipInfo
|
||||
top int
|
||||
}
|
||||
|
||||
// Create a new Stack
|
||||
func NewStack() *Stack {
|
||||
s := &Stack{top: -1}
|
||||
s.st = s.stA[:0]
|
||||
return s
|
||||
}
|
||||
|
||||
// Return the number of items in the Stack
|
||||
func (s *Stack) Len() int {
|
||||
return (s.top + 1)
|
||||
}
|
||||
|
||||
// View the top item on the Stack
|
||||
func (s *Stack) Peek() *skipInfo {
|
||||
if s.top == -1 {
|
||||
return nil
|
||||
}
|
||||
return &s.st[s.top]
|
||||
}
|
||||
|
||||
// Pop the top item of the Stack and return it
|
||||
func (s *Stack) Pop() *skipInfo {
|
||||
if s.top == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
s.top--
|
||||
return &s.st[(s.top + 1)]
|
||||
}
|
||||
|
||||
// Push a value onto the top of the Stack
|
||||
func (s *Stack) Push(value skipInfo) {
|
||||
s.top++
|
||||
if len(s.st) <= s.top {
|
||||
s.st = append(s.st, value)
|
||||
} else {
|
||||
s.st[s.top] = value
|
||||
}
|
||||
}
|
37
jsn/tree.go
Normal file
37
jsn/tree.go
Normal file
@ -0,0 +1,37 @@
|
||||
package jsn
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
func Tree(v []byte) (map[string]interface{}, bool, error) {
|
||||
dec := json.NewDecoder(bytes.NewReader(v))
|
||||
array := false
|
||||
|
||||
// read open bracket
|
||||
|
||||
for i := range v {
|
||||
if v[i] != ' ' {
|
||||
array = (v[i] == '[')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if array {
|
||||
if _, err := dec.Token(); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
// while the array contains values
|
||||
var m map[string]interface{}
|
||||
|
||||
// decode an array value (Message)
|
||||
err := dec.Decode(&m)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
return m, array, nil
|
||||
}
|
Reference in New Issue
Block a user