From 85e939636f57b6a88250ff65c2b81e77a9047dd4 Mon Sep 17 00:00:00 2001 From: Harshavardhana Date: Wed, 6 Feb 2019 18:34:42 -0800 Subject: [PATCH] Fix JSON parser handling for certain objects (#7162) This PR also adds some comments and simplifies the code. Primary handling is done to ensure that we make sure to honor cached buffer. Added unit tests as well Fixes #7141 --- cmd/object-handlers.go | 2 +- pkg/s3select/json/data/10.json | 12 + pkg/s3select/json/data/11.json | 8 + pkg/s3select/json/data/12.json | 5 + pkg/s3select/json/data/2.json | 1 + pkg/s3select/json/data/3.json | 1 + pkg/s3select/json/data/4.json | 26 + pkg/s3select/json/data/5.json | 5 + pkg/s3select/json/data/6.json | 1 + pkg/s3select/json/data/7.json | 3 + pkg/s3select/json/data/8.json | 2 + pkg/s3select/json/data/9.json | 6 + pkg/s3select/json/reader.go | 192 +------ pkg/s3select/json/reader_test.go | 49 ++ vendor/github.com/bcicen/jstream/LICENSE | 22 + vendor/github.com/bcicen/jstream/README.md | 93 ++++ vendor/github.com/bcicen/jstream/decoder.go | 554 +++++++++++++++++++ vendor/github.com/bcicen/jstream/errors.go | 41 ++ vendor/github.com/bcicen/jstream/jstream.png | Bin 0 -> 32719 bytes vendor/github.com/bcicen/jstream/scanner.go | 107 ++++ vendor/github.com/bcicen/jstream/scratch.go | 44 ++ vendor/vendor.json | 6 + 22 files changed, 1015 insertions(+), 165 deletions(-) create mode 100644 pkg/s3select/json/data/10.json create mode 100644 pkg/s3select/json/data/11.json create mode 100644 pkg/s3select/json/data/12.json create mode 100644 pkg/s3select/json/data/2.json create mode 100644 pkg/s3select/json/data/3.json create mode 100644 pkg/s3select/json/data/4.json create mode 100644 pkg/s3select/json/data/5.json create mode 100644 pkg/s3select/json/data/6.json create mode 100644 pkg/s3select/json/data/7.json create mode 100644 pkg/s3select/json/data/8.json create mode 100644 pkg/s3select/json/data/9.json create mode 100644 pkg/s3select/json/reader_test.go create mode 100644 vendor/github.com/bcicen/jstream/LICENSE create mode 100644 vendor/github.com/bcicen/jstream/README.md create mode 100644 vendor/github.com/bcicen/jstream/decoder.go create mode 100644 vendor/github.com/bcicen/jstream/errors.go create mode 100644 vendor/github.com/bcicen/jstream/jstream.png create mode 100644 vendor/github.com/bcicen/jstream/scanner.go create mode 100644 vendor/github.com/bcicen/jstream/scratch.go diff --git a/cmd/object-handlers.go b/cmd/object-handlers.go index 6cb708453..bfadf289f 100644 --- a/cmd/object-handlers.go +++ b/cmd/object-handlers.go @@ -198,7 +198,7 @@ func (api objectAPIHandlers) SelectObjectContentHandler(w http.ResponseWriter, r w.WriteHeader(serr.HTTPStatusCode()) w.Write(s3select.NewErrorMessage(serr.ErrorCode(), serr.ErrorMessage())) } else { - writeErrorResponse(w, ErrInternalError, r.URL, guessIsBrowserReq(r)) + writeErrorResponse(w, toAPIErrorCode(ctx, err), r.URL, guessIsBrowserReq(r)) } return } diff --git a/pkg/s3select/json/data/10.json b/pkg/s3select/json/data/10.json new file mode 100644 index 000000000..57bf5e805 --- /dev/null +++ b/pkg/s3select/json/data/10.json @@ -0,0 +1,12 @@ +[ + { + "key_1": "value", + "key_2": "value" + } +] +[ + { + "key_1": "value2", + "key_2": "value3" + } +] diff --git a/pkg/s3select/json/data/11.json b/pkg/s3select/json/data/11.json new file mode 100644 index 000000000..e63d62d87 --- /dev/null +++ b/pkg/s3select/json/data/11.json @@ -0,0 +1,8 @@ +"a" +1 +3.145 +["a"] +{} +{ + "a": 1 +} diff --git a/pkg/s3select/json/data/12.json b/pkg/s3select/json/data/12.json new file mode 100644 index 000000000..8c175ec23 --- /dev/null +++ b/pkg/s3select/json/data/12.json @@ -0,0 +1,5 @@ +{ + "a": 1 +}{ + "b": 2 +} diff --git a/pkg/s3select/json/data/2.json b/pkg/s3select/json/data/2.json new file mode 100644 index 000000000..45fed0992 --- /dev/null +++ b/pkg/s3select/json/data/2.json @@ -0,0 +1 @@ +{"text": "hello world\\n2nd line"} diff --git a/pkg/s3select/json/data/3.json b/pkg/s3select/json/data/3.json new file mode 100644 index 000000000..949390e27 --- /dev/null +++ b/pkg/s3select/json/data/3.json @@ -0,0 +1 @@ +{"hello":"wor{l}d"} diff --git a/pkg/s3select/json/data/4.json b/pkg/s3select/json/data/4.json new file mode 100644 index 000000000..ef2b65474 --- /dev/null +++ b/pkg/s3select/json/data/4.json @@ -0,0 +1,26 @@ +{ + "id": "0001", + "type": "donut", + "name": "Cake", + "ppu": 0.55, + "batters": + { + "batter": + [ + { "id": "1001", "type": "Regular" }, + { "id": "1002", "type": "Chocolate" }, + { "id": "1003", "type": "Blueberry" }, + { "id": "1004", "type": "Devil's Food" } + ] + }, + "topping": + [ + { "id": "5001", "type": "None" }, + { "id": "5002", "type": "Glazed" }, + { "id": "5005", "type": "Sugar" }, + { "id": "5007", "type": "Powdered Sugar" }, + { "id": "5006", "type": "Chocolate with Sprinkles" }, + { "id": "5003", "type": "Chocolate" }, + { "id": "5004", "type": "Maple" } + ] +} diff --git a/pkg/s3select/json/data/5.json b/pkg/s3select/json/data/5.json new file mode 100644 index 000000000..ef69872d7 --- /dev/null +++ b/pkg/s3select/json/data/5.json @@ -0,0 +1,5 @@ +{ + "foo": { + "bar": "baz" + } +} diff --git a/pkg/s3select/json/data/6.json b/pkg/s3select/json/data/6.json new file mode 100644 index 000000000..31e3e4330 --- /dev/null +++ b/pkg/s3select/json/data/6.json @@ -0,0 +1 @@ +{ "name": "John", "age":28, "hobby": { "name": "chess", "type": "boardgame" }} diff --git a/pkg/s3select/json/data/7.json b/pkg/s3select/json/data/7.json new file mode 100644 index 000000000..60e603e38 --- /dev/null +++ b/pkg/s3select/json/data/7.json @@ -0,0 +1,3 @@ +{"name":"Michael", "age": 31} +{"name":"Andy", "age": 30} +{"name":"Justin", "age": 19} diff --git a/pkg/s3select/json/data/8.json b/pkg/s3select/json/data/8.json new file mode 100644 index 000000000..cc9350e5f --- /dev/null +++ b/pkg/s3select/json/data/8.json @@ -0,0 +1,2 @@ +{"a":"}" +} diff --git a/pkg/s3select/json/data/9.json b/pkg/s3select/json/data/9.json new file mode 100644 index 000000000..f9ab70461 --- /dev/null +++ b/pkg/s3select/json/data/9.json @@ -0,0 +1,6 @@ +[ + { + "key_1": "value", + "key_2": "value" + } +] diff --git a/pkg/s3select/json/reader.go b/pkg/s3select/json/reader.go index fb4e15ba2..06971e2ab 100644 --- a/pkg/s3select/json/reader.go +++ b/pkg/s3select/json/reader.go @@ -17,186 +17,48 @@ package json import ( - "bytes" + "encoding/json" "io" - "io/ioutil" - "strconv" "github.com/minio/minio/pkg/s3select/sql" - "github.com/tidwall/gjson" + + "github.com/bcicen/jstream" "github.com/tidwall/sjson" ) -func toSingleLineJSON(input string, currentKey string, result gjson.Result) (output string, err error) { - switch { - case result.IsObject(): - result.ForEach(func(key, value gjson.Result) bool { - jsonKey := key.String() - if currentKey != "" { - jsonKey = currentKey + "." + key.String() - } - output, err = toSingleLineJSON(input, jsonKey, value) - input = output - return err == nil - }) - case result.IsArray(): - i := 0 - result.ForEach(func(key, value gjson.Result) bool { - if currentKey == "" { - panic("currentKey is empty") - } - - indexKey := currentKey + "." + strconv.Itoa(i) - output, err = toSingleLineJSON(input, indexKey, value) - input = output - i++ - return err == nil - }) - default: - output, err = sjson.Set(input, currentKey, result.Value()) - } - - return output, err -} - -type objectReader struct { - reader io.Reader - err error - - p []byte - start int - end int - - escaped bool - quoteOpened bool - curlyCount uint64 - endOfObject bool -} - -func (or *objectReader) objectEndIndex(p []byte, length int) int { - for i := 0; i < length; i++ { - if p[i] == '\\' { - or.escaped = !or.escaped - continue - } - - if p[i] == '"' && !or.escaped { - or.quoteOpened = !or.quoteOpened - } - - or.escaped = false - - switch p[i] { - case '{': - if !or.quoteOpened { - or.curlyCount++ - } - case '}': - if or.quoteOpened || or.curlyCount == 0 { - break - } - - if or.curlyCount--; or.curlyCount == 0 { - return i + 1 - } - } - } - - return -1 -} - -func (or *objectReader) Read(p []byte) (n int, err error) { - if or.endOfObject { - return 0, io.EOF - } - - if or.p != nil { - n = copy(p, or.p[or.start:or.end]) - or.start += n - if or.start == or.end { - // made full copy. - or.p = nil - or.start = 0 - or.end = 0 - } - } else { - if or.err != nil { - return 0, or.err - } - - n, err = or.reader.Read(p) - or.err = err - switch err { - case nil: - case io.EOF, io.ErrUnexpectedEOF, io.ErrClosedPipe: - or.err = io.EOF - default: - return 0, err - } - } - - index := or.objectEndIndex(p, n) - if index == -1 || index == n { - return n, nil - } - - or.endOfObject = true - if or.p == nil { - or.p = p - or.start = index - or.end = n - } else { - or.start -= index - } - - return index, nil -} - -func (or *objectReader) Reset() error { - or.endOfObject = false - - if or.p != nil { - return nil - } - - return or.err -} - // Reader - JSON record reader for S3Select. type Reader struct { - args *ReaderArgs - objectReader *objectReader - readCloser io.ReadCloser + args *ReaderArgs + decoder *jstream.Decoder + valueCh chan *jstream.MetaValue + readCloser io.ReadCloser } // Read - reads single record. func (r *Reader) Read() (sql.Record, error) { - if err := r.objectReader.Reset(); err != nil { - return nil, err + v, ok := <-r.valueCh + if !ok { + if err := r.decoder.Err(); err != nil { + return nil, errJSONParsingError(err) + } + return nil, io.EOF } - data, err := ioutil.ReadAll(r.objectReader) - if err != nil { - return nil, errJSONParsingError(err) - } + var data []byte + var err error - data = bytes.TrimSpace(data) - if len(data) == 0 { - return nil, io.EOF + if v.ValueType == jstream.Object { + data, err = json.Marshal(v.Value) + } else { + // To be AWS S3 compatible + // Select for JSON needs to output non-object JSON as single column value + // i.e. a map with `_1` as key and value as the non-object. + data, err = sjson.SetBytes(data, "_1", v.Value) } - - if !gjson.ValidBytes(data) { + if err != nil { return nil, errJSONParsingError(err) } - if bytes.Count(data, []byte("\n")) > 0 { - var s string - if s, err = toSingleLineJSON("", "", gjson.ParseBytes(data)); err != nil { - return nil, errJSONParsingError(err) - } - data = []byte(s) - } - return &Record{ data: data, }, nil @@ -209,9 +71,11 @@ func (r *Reader) Close() error { // NewReader - creates new JSON reader using readCloser. func NewReader(readCloser io.ReadCloser, args *ReaderArgs) *Reader { + d := jstream.NewDecoder(readCloser, 0) return &Reader{ - args: args, - objectReader: &objectReader{reader: readCloser}, - readCloser: readCloser, + args: args, + decoder: d, + valueCh: d.Stream(), + readCloser: readCloser, } } diff --git a/pkg/s3select/json/reader_test.go b/pkg/s3select/json/reader_test.go new file mode 100644 index 000000000..cb83a7c2e --- /dev/null +++ b/pkg/s3select/json/reader_test.go @@ -0,0 +1,49 @@ +/* + * Minio Cloud Storage, (C) 2019 Minio, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package json + +import ( + "io" + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +func TestNewReader(t *testing.T) { + files, err := ioutil.ReadDir("data") + if err != nil { + t.Fatal(err) + } + for _, file := range files { + f, err := os.Open(filepath.Join("data", file.Name())) + if err != nil { + t.Fatal(err) + } + r := NewReader(f, &ReaderArgs{}) + for { + _, err = r.Read() + if err != nil { + break + } + } + r.Close() + if err != io.EOF { + t.Fatalf("Reading failed with %s, %s", err, file.Name()) + } + } +} diff --git a/vendor/github.com/bcicen/jstream/LICENSE b/vendor/github.com/bcicen/jstream/LICENSE new file mode 100644 index 000000000..1c5d82df6 --- /dev/null +++ b/vendor/github.com/bcicen/jstream/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2018 Bradley Cicenas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/vendor/github.com/bcicen/jstream/README.md b/vendor/github.com/bcicen/jstream/README.md new file mode 100644 index 000000000..d8f43aa7a --- /dev/null +++ b/vendor/github.com/bcicen/jstream/README.md @@ -0,0 +1,93 @@ +

jstream

+ +# + +[![GoDoc](https://godoc.org/github.com/bcicen/jstream?status.svg)](https://godoc.org/github.com/bcicen/jstream) + + +`jstream` is a streaming JSON parser and value extraction library for Go. + +Unlike most JSON parsers, `jstream` is document position- and depth-aware -- this enables the extraction of values at a specified depth, eliminating the overhead of allocating encompassing arrays or objects; e.g: + +Using the below example document: +jstream + +we can choose to extract and act only the objects within the top-level array: +```go +f, _ := os.Open("input.json") +decoder := jstream.NewDecoder(f, 1) // extract JSON values at a depth level of 1 +for mv := range decoder.Stream() { + fmt.Printf("%v\n ", mv.Value) +} +``` + +output: +``` +map[desc:RGB colors:[red green blue]] +map[desc:CMYK colors:[cyan magenta yellow black]] +``` + +likewise, increasing depth level to `3` yields: +``` +red +green +blue +cyan +magenta +yellow +black +``` + +optionally, kev:value pairs can be emitted as an individual struct: +```go +decoder := jstream.NewDecoder(f, 2).EmitKV() // enable KV streaming at a depth level of 2 +``` + +``` +jstream.KV{desc RGB} +jstream.KV{colors [red green blue]} +jstream.KV{desc CMYK} +jstream.KV{colors [cyan magenta yellow black]} +``` + +## Installing + +```bash +go get github.com/bcicen/jstream +``` + +## Commandline + +`jstream` comes with a cli tool for quick viewing of parsed values from JSON input: + +```bash +cat input.json | jstream -v -d 1 +depth start end type | value + +1 004 069 object | {"colors":["red","green","blue"],"desc":"RGB"} +1 073 153 object | {"colors":["cyan","magenta","yellow","black"],"desc":"CMYK"} +``` + +### Options + +Opt | Description +--- | --- +-d \ | emit values at depth n. if n < 0, all values will be emitted +-v | output depth and offset details for each value +-h | display help dialog + +## Benchmarks + +Obligatory benchmarks performed on files with arrays of objects, where the decoded objects are to be extracted. + +Two file sizes are used -- regular (1.6mb, 1000 objects) and large (128mb, 100000 objects) + +input size | lib | MB/s | Allocated +--- | --- | --- | --- +regular | standard | 97 | 3.6MB +regular | jstream | 175 | 2.1MB +large | standard | 92 | 305MB +large | jstream | 404 | 69MB + +In a real world scenario, including initialization and reader overhead from varying blob sizes, performance can be expected as below: +jstream diff --git a/vendor/github.com/bcicen/jstream/decoder.go b/vendor/github.com/bcicen/jstream/decoder.go new file mode 100644 index 000000000..1c596e22c --- /dev/null +++ b/vendor/github.com/bcicen/jstream/decoder.go @@ -0,0 +1,554 @@ +package jstream + +import ( + "bytes" + "encoding/json" + "io" + "strconv" + "sync/atomic" + "unicode/utf16" +) + +// ValueType - defines the type of each JSON value +type ValueType int + +// Different types of JSON value +const ( + Unknown ValueType = iota + Null + String + Number + Boolean + Array + Object +) + +// MetaValue wraps a decoded interface value with the document +// position and depth at which the value was parsed +type MetaValue struct { + Offset int + Length int + Depth int + Value interface{} + ValueType ValueType +} + +// KV contains a key and value pair parsed from a decoded object +type KV struct { + Key string `json:"key"` + Value interface{} `json:"value"` +} + +// KVS - represents key values in an JSON object +type KVS []KV + +// MarshalJSON - implements converting slice of KVS into a JSON object +// with multiple keys and values. +func (kvs KVS) MarshalJSON() ([]byte, error) { + b := new(bytes.Buffer) + b.Write([]byte("{")) + for i, kv := range kvs { + b.Write([]byte("\"" + kv.Key + "\"" + ":")) + valBuf, err := json.Marshal(kv.Value) + if err != nil { + return nil, err + } + b.Write(valBuf) + if i < len(kvs)-1 { + b.Write([]byte(",")) + } + } + b.Write([]byte("}")) + return b.Bytes(), nil +} + +// Decoder wraps an io.Reader to provide incremental decoding of +// JSON values +type Decoder struct { + *scanner + emitDepth int + emitKV bool + emitRecursive bool + + depth int + scratch *scratch + metaCh chan *MetaValue + err error + + // follow line position to add context to errors + lineNo int + lineStart int64 +} + +// NewDecoder creates new Decoder to read JSON values at the provided +// emitDepth from the provider io.Reader. +// If emitDepth is < 0, values at every depth will be emitted. +func NewDecoder(r io.Reader, emitDepth int) *Decoder { + d := &Decoder{ + scanner: newScanner(r), + emitDepth: emitDepth, + scratch: &scratch{data: make([]byte, 1024)}, + metaCh: make(chan *MetaValue, 128), + } + if emitDepth < 0 { + d.emitDepth = 0 + d.emitRecursive = true + } + return d +} + +// EmitKV enables emitting a jstream.KV struct when the items(s) parsed +// at configured emit depth are within a JSON object. By default, only +// the object values are emitted. +func (d *Decoder) EmitKV() *Decoder { + d.emitKV = true + return d +} + +// Recursive enables emitting all values at a depth higher than the +// configured emit depth; e.g. if an array is found at emit depth, all +// values within the array are emitted to the stream, then the array +// containing those values is emitted. +func (d *Decoder) Recursive() *Decoder { + d.emitRecursive = true + return d +} + +// Stream begins decoding from the underlying reader and returns a +// streaming MetaValue channel for JSON values at the configured emitDepth. +func (d *Decoder) Stream() chan *MetaValue { + go d.decode() + return d.metaCh +} + +// Pos returns the number of bytes consumed from the underlying reader +func (d *Decoder) Pos() int { return int(d.pos) } + +// Err returns the most recent decoder error if any, or nil +func (d *Decoder) Err() error { return d.err } + +// Decode parses the JSON-encoded data and returns an interface value +func (d *Decoder) decode() { + defer close(d.metaCh) + d.skipSpaces() + for d.pos < atomic.LoadInt64(&d.end) { + _, err := d.emitAny() + if err != nil { + d.err = err + break + } + d.skipSpaces() + } +} + +func (d *Decoder) emitAny() (interface{}, error) { + if d.pos >= atomic.LoadInt64(&d.end) { + return nil, d.mkError(ErrUnexpectedEOF) + } + offset := d.pos - 1 + i, t, err := d.any() + if d.willEmit() { + d.metaCh <- &MetaValue{ + Offset: int(offset), + Length: int(d.pos - offset), + Depth: d.depth, + Value: i, + ValueType: t, + } + } + return i, err +} + +// return whether, at the current depth, the value being decoded will +// be emitted to stream +func (d *Decoder) willEmit() bool { + if d.emitRecursive { + return d.depth >= d.emitDepth + } + return d.depth == d.emitDepth +} + +// any used to decode any valid JSON value, and returns an +// interface{} that holds the actual data +func (d *Decoder) any() (interface{}, ValueType, error) { + c := d.cur() + + switch c { + case '"': + i, err := d.string() + return i, String, err + case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9': + i, err := d.number() + return i, Number, err + case '-': + if c = d.next(); c < '0' && c > '9' { + return nil, Unknown, d.mkError(ErrSyntax, "in negative numeric literal") + } + n, err := d.number() + if err != nil { + return nil, Unknown, err + } + return -n, Number, nil + case 'f': + if d.remaining() < 4 { + return nil, Unknown, d.mkError(ErrUnexpectedEOF) + } + if d.next() == 'a' && d.next() == 'l' && d.next() == 's' && d.next() == 'e' { + return false, Boolean, nil + } + return nil, Unknown, d.mkError(ErrSyntax, "in literal false") + case 't': + if d.remaining() < 3 { + return nil, Unknown, d.mkError(ErrUnexpectedEOF) + } + if d.next() == 'r' && d.next() == 'u' && d.next() == 'e' { + return true, Boolean, nil + } + return nil, Unknown, d.mkError(ErrSyntax, "in literal true") + case 'n': + if d.remaining() < 3 { + return nil, Unknown, d.mkError(ErrUnexpectedEOF) + } + if d.next() == 'u' && d.next() == 'l' && d.next() == 'l' { + return nil, Null, nil + } + return nil, Unknown, d.mkError(ErrSyntax, "in literal null") + case '[': + i, err := d.array() + return i, Array, err + case '{': + i, err := d.object() + return i, Object, err + default: + return nil, Unknown, d.mkError(ErrSyntax, "looking for beginning of value") + } +} + +// string called by `any` or `object`(for map keys) after reading `"` +func (d *Decoder) string() (string, error) { + d.scratch.reset() + + var ( + c = d.next() + ) + +scan: + for { + switch { + case c == '"': + return string(d.scratch.bytes()), nil + case c == '\\': + c = d.next() + goto scan_esc + case c < 0x20: + return "", d.mkError(ErrSyntax, "in string literal") + // Coerce to well-formed UTF-8. + default: + d.scratch.add(c) + if d.remaining() == 0 { + return "", d.mkError(ErrSyntax, "in string literal") + } + c = d.next() + } + } + +scan_esc: + switch c { + case '"', '\\', '/', '\'': + d.scratch.add(c) + case 'u': + goto scan_u + case 'b': + d.scratch.add('\b') + case 'f': + d.scratch.add('\f') + case 'n': + d.scratch.add('\n') + case 'r': + d.scratch.add('\r') + case 't': + d.scratch.add('\t') + default: + return "", d.mkError(ErrSyntax, "in string escape code") + } + c = d.next() + goto scan + +scan_u: + r := d.u4() + if r < 0 { + return "", d.mkError(ErrSyntax, "in unicode escape sequence") + } + + // check for proceeding surrogate pair + c = d.next() + if !utf16.IsSurrogate(r) || c != '\\' { + d.scratch.addRune(r) + goto scan + } + if c = d.next(); c != 'u' { + d.scratch.addRune(r) + goto scan_esc + } + + r2 := d.u4() + if r2 < 0 { + return "", d.mkError(ErrSyntax, "in unicode escape sequence") + } + + // write surrogate pair + d.scratch.addRune(utf16.DecodeRune(r, r2)) + c = d.next() + goto scan +} + +// u4 reads four bytes following a \u escape +func (d *Decoder) u4() rune { + // logic taken from: + // github.com/buger/jsonparser/blob/master/escape.go#L20 + var h [4]int + for i := 0; i < 4; i++ { + c := d.next() + switch { + case c >= '0' && c <= '9': + h[i] = int(c - '0') + case c >= 'A' && c <= 'F': + h[i] = int(c - 'A' + 10) + case c >= 'a' && c <= 'f': + h[i] = int(c - 'a' + 10) + default: + return -1 + } + } + return rune(h[0]<<12 + h[1]<<8 + h[2]<<4 + h[3]) +} + +// number called by `any` after reading number between 0 to 9 +func (d *Decoder) number() (float64, error) { + d.scratch.reset() + + var ( + c = d.cur() + n float64 + isFloat bool + ) + + // digits first + switch { + case c == '0': + d.scratch.add(c) + c = d.next() + case '1' <= c && c <= '9': + for ; c >= '0' && c <= '9'; c = d.next() { + n = 10*n + float64(c-'0') + d.scratch.add(c) + } + } + + // . followed by 1 or more digits + if c == '.' { + isFloat = true + d.scratch.add(c) + + // first char following must be digit + if c = d.next(); c < '0' && c > '9' { + return 0, d.mkError(ErrSyntax, "after decimal point in numeric literal") + } + d.scratch.add(c) + + for { + if d.remaining() == 0 { + return 0, d.mkError(ErrUnexpectedEOF) + } + if c = d.next(); c < '0' || c > '9' { + break + } + d.scratch.add(c) + } + } + + // e or E followed by an optional - or + and + // 1 or more digits. + if c == 'e' || c == 'E' { + isFloat = true + d.scratch.add(c) + + if c = d.next(); c == '+' || c == '-' { + d.scratch.add(c) + if c = d.next(); c < '0' || c > '9' { + return 0, d.mkError(ErrSyntax, "in exponent of numeric literal") + } + d.scratch.add(c) + } + for ; c >= '0' && c <= '9'; c = d.next() { + d.scratch.add(c) + } + } + + if isFloat { + var ( + err error + sn string + ) + sn = string(d.scratch.bytes()) + if n, err = strconv.ParseFloat(sn, 64); err != nil { + return 0, err + } + } + + d.back() + return n, nil +} + +// array accept valid JSON array value +func (d *Decoder) array() ([]interface{}, error) { + d.depth++ + + var ( + c byte + v interface{} + err error + array = make([]interface{}, 0) + ) + + // look ahead for ] - if the array is empty. + if c = d.skipSpaces(); c == ']' { + goto out + } + +scan: + if v, err = d.emitAny(); err != nil { + goto out + } + + if d.depth > d.emitDepth { // skip alloc for array if it won't be emitted + array = append(array, v) + } + + // next token must be ',' or ']' + switch c = d.skipSpaces(); c { + case ',': + d.skipSpaces() + goto scan + case ']': + goto out + default: + err = d.mkError(ErrSyntax, "after array element") + } + +out: + d.depth-- + return array, err +} + +// object accept valid JSON array value +func (d *Decoder) object() (KVS, error) { + d.depth++ + + var ( + c byte + k string + v interface{} + t ValueType + err error + obj KVS + ) + + // skip allocating map if it will not be emitted + if d.depth > d.emitDepth { + obj = make(KVS, 0) + } + + // if the object has no keys + if c = d.skipSpaces(); c == '}' { + goto out + } + +scan: + for { + offset := d.pos - 1 + + // read string key + if c != '"' { + err = d.mkError(ErrSyntax, "looking for beginning of object key string") + break + } + if k, err = d.string(); err != nil { + break + } + + // read colon before value + if c = d.skipSpaces(); c != ':' { + err = d.mkError(ErrSyntax, "after object key") + break + } + + // read value + d.skipSpaces() + if d.emitKV { + if v, t, err = d.any(); err != nil { + break + } + if d.willEmit() { + d.metaCh <- &MetaValue{ + Offset: int(offset), + Length: int(d.pos - offset), + Depth: d.depth, + Value: KV{k, v}, + ValueType: t, + } + } + } else { + if v, err = d.emitAny(); err != nil { + break + } + } + + if obj != nil { + obj = append(obj, KV{k, v}) + } + + // next token must be ',' or '}' + switch c = d.skipSpaces(); c { + case '}': + goto out + case ',': + c = d.skipSpaces() + goto scan + default: + err = d.mkError(ErrSyntax, "after object key:value pair") + } + } + +out: + d.depth-- + return obj, err +} + +// returns the next char after white spaces +func (d *Decoder) skipSpaces() byte { + for d.pos < atomic.LoadInt64(&d.end) { + switch c := d.next(); c { + case '\n': + d.lineStart = d.pos + d.lineNo++ + continue + case ' ', '\t', '\r': + continue + default: + return c + } + } + return 0 +} + +// create syntax errors at current position, with optional context +func (d *Decoder) mkError(err SyntaxError, context ...string) error { + if len(context) > 0 { + err.context = context[0] + } + err.atChar = d.cur() + err.pos[0] = d.lineNo + 1 + err.pos[1] = int(d.pos - d.lineStart) + return err +} diff --git a/vendor/github.com/bcicen/jstream/errors.go b/vendor/github.com/bcicen/jstream/errors.go new file mode 100644 index 000000000..19e3b1f62 --- /dev/null +++ b/vendor/github.com/bcicen/jstream/errors.go @@ -0,0 +1,41 @@ +package jstream + +import ( + "fmt" + "strconv" +) + +// Predefined errors +var ( + ErrSyntax = SyntaxError{msg: "invalid character"} + ErrUnexpectedEOF = SyntaxError{msg: "unexpected end of JSON input"} +) + +type errPos [2]int // line number, byte offset where error occurred + +type SyntaxError struct { + msg string // description of error + context string // additional error context + pos errPos + atChar byte +} + +func (e SyntaxError) Error() string { + loc := fmt.Sprintf("%s [%d,%d]", quoteChar(e.atChar), e.pos[0], e.pos[1]) + return fmt.Sprintf("%s %s: %s", e.msg, e.context, loc) +} + +// quoteChar formats c as a quoted character literal +func quoteChar(c byte) string { + // special cases - different from quoted strings + if c == '\'' { + return `'\''` + } + if c == '"' { + return `'"'` + } + + // use quoted string with different quotation marks + s := strconv.Quote(string(c)) + return "'" + s[1:len(s)-1] + "'" +} diff --git a/vendor/github.com/bcicen/jstream/jstream.png b/vendor/github.com/bcicen/jstream/jstream.png new file mode 100644 index 0000000000000000000000000000000000000000..38d6e4486ee9a39543806aa112226c2499ac9141 GIT binary patch literal 32719 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 1) +) + +type scanner struct { + pos int64 // position in reader + ipos int64 // internal buffer position + ifill int64 // internal buffer fill + end int64 + buf [chunk + 1]byte // internal buffer (with a lookback size of 1) + nbuf [chunk]byte // next internal buffer + fillReq chan struct{} + fillReady chan int64 +} + +func newScanner(r io.Reader) *scanner { + sr := &scanner{ + end: maxInt, + fillReq: make(chan struct{}), + fillReady: make(chan int64), + } + + go func() { + var rpos int64 // total bytes read into buffer + + for _ = range sr.fillReq { + scan: + n, err := r.Read(sr.nbuf[:]) + + if n == 0 { + switch err { + case io.EOF: // reader is exhausted + atomic.StoreInt64(&sr.end, rpos) + close(sr.fillReady) + return + case nil: // no data and no error, retry fill + goto scan + default: + panic(err) + } + } + + rpos += int64(n) + sr.fillReady <- int64(n) + } + }() + + sr.fillReq <- struct{}{} // initial fill + + return sr +} + +// remaining returns the number of unread bytes +// if EOF for the underlying reader has not yet been found, +// maximum possible integer value will be returned +func (s *scanner) remaining() int64 { + if atomic.LoadInt64(&s.end) == maxInt { + return maxInt + } + return atomic.LoadInt64(&s.end) - s.pos +} + +// read byte at current position (without advancing) +func (s *scanner) cur() byte { return s.buf[s.ipos] } + +// read next byte +func (s *scanner) next() byte { + if s.pos >= atomic.LoadInt64(&s.end) { + return byte(0) + } + s.ipos++ + + if s.ipos > s.ifill { // internal buffer is exhausted + s.ifill = <-s.fillReady + s.buf[0] = s.buf[len(s.buf)-1] // copy current last item to guarantee lookback + copy(s.buf[1:], s.nbuf[:]) // copy contents of pre-filled next buffer + s.ipos = 1 // move to beginning of internal buffer + + // request next fill to be prepared + if s.end == maxInt { + s.fillReq <- struct{}{} + } + } + + s.pos++ + return s.buf[s.ipos] +} + +// back undoes a previous call to next(), moving backward one byte in the internal buffer. +// as we only guarantee a lookback buffer size of one, any subsequent calls to back() +// before calling next() may panic +func (s *scanner) back() { + if s.ipos <= 0 { + panic("back buffer exhausted") + } + s.ipos-- + s.pos-- +} diff --git a/vendor/github.com/bcicen/jstream/scratch.go b/vendor/github.com/bcicen/jstream/scratch.go new file mode 100644 index 000000000..9e29e3e43 --- /dev/null +++ b/vendor/github.com/bcicen/jstream/scratch.go @@ -0,0 +1,44 @@ +package jstream + +import ( + "unicode/utf8" +) + +type scratch struct { + data []byte + fill int +} + +// reset scratch buffer +func (s *scratch) reset() { s.fill = 0 } + +// bytes returns the written contents of scratch buffer +func (s *scratch) bytes() []byte { return s.data[0:s.fill] } + +// grow scratch buffer +func (s *scratch) grow() { + ndata := make([]byte, cap(s.data)*2) + copy(ndata, s.data[:]) + s.data = ndata +} + +// append single byte to scratch buffer +func (s *scratch) add(c byte) { + if s.fill+1 >= cap(s.data) { + s.grow() + } + + s.data[s.fill] = c + s.fill++ +} + +// append encoded rune to scratch buffer +func (s *scratch) addRune(r rune) int { + if s.fill+utf8.UTFMax >= cap(s.data) { + s.grow() + } + + n := utf8.EncodeRune(s.data[s.fill:], r) + s.fill += n + return n +} diff --git a/vendor/vendor.json b/vendor/vendor.json index 32f1514cb..4a62aaa66 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -115,6 +115,12 @@ "revision": "6fe16293d6b7af4f5c2450714c5b4825c8ad040c", "revisionTime": "2017-09-25T03:23:15Z" }, + { + "checksumSHA1": "mSo9Sti7F6+laUs4NLx/p23rHD0=", + "path": "github.com/bcicen/jstream", + "revision": "f306cd3e1fa602a1b513114521a0d6a5a9d5c919", + "revisionTime": "2019-02-06T02:23:53Z" + }, { "checksumSHA1": "0rido7hYHQtfq3UJzVT5LClLAWc=", "path": "github.com/beorn7/perks/quantile",