331 lines
9.6 KiB
Go
331 lines
9.6 KiB
Go
// Copyright 2023 Dolthub, 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.
|
|
|
|
// Copyright 2017 The Cockroach Authors.
|
|
//
|
|
// Use of this software is governed by the Business Source License
|
|
// included in the file licenses/BSL.txt.
|
|
//
|
|
// As of the Change Date specified in that file, in accordance with
|
|
// the Business Source License, use of this software will be governed
|
|
// by the Apache License, Version 2.0, included in the file
|
|
// licenses/APL.txt.
|
|
|
|
package json
|
|
|
|
import (
|
|
"github.com/cockroachdb/apd/v3"
|
|
"github.com/cockroachdb/errors"
|
|
|
|
"github.com/dolthub/doltgresql/postgres/parser/encoding"
|
|
)
|
|
|
|
// This file implements the format described in the JSONB encoding RFC.
|
|
|
|
const offlenStride = 32
|
|
|
|
const arrayContainerTag = 0x80000000
|
|
const objectContainerTag = 0x40000000
|
|
const scalarContainerTag = 0x20000000
|
|
|
|
const containerHeaderTypeMask = 0xE0000000
|
|
const containerHeaderLenMask = 0x1FFFFFFF
|
|
|
|
const maxByteLength = int(jEntryOffLenMask)
|
|
|
|
const containerHeaderLen = 4
|
|
const jEntryLen = 4
|
|
|
|
// checkLength ensures that an encoded value is not too long to fit into the
|
|
// JEntry header. This should never come up, since it would require a ~250MB
|
|
// JSON value, but check it just to be safe.
|
|
func checkLength(length int) error {
|
|
if length > maxByteLength {
|
|
return errors.AssertionFailedf("JSON value too large: %d bytes", errors.Safe(length))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Note: the encoding of each of null, true, and false are the encoding of length 0.
|
|
// Their values are purely dictated by their type.
|
|
func (jsonNull) encode(appendTo []byte) (e jEntry, b []byte, err error) {
|
|
return nullJEntry, appendTo, nil
|
|
}
|
|
|
|
func (jsonTrue) encode(appendTo []byte) (e jEntry, b []byte, err error) {
|
|
return trueJEntry, appendTo, nil
|
|
}
|
|
|
|
func (jsonFalse) encode(appendTo []byte) (e jEntry, b []byte, err error) {
|
|
return falseJEntry, appendTo, nil
|
|
}
|
|
|
|
func (j jsonString) encode(appendTo []byte) (e jEntry, b []byte, err error) {
|
|
if err := checkLength(len(j)); err != nil {
|
|
return jEntry{}, b, err
|
|
}
|
|
return makeStringJEntry(len(j)), append(appendTo, []byte(j)...), nil
|
|
}
|
|
|
|
func (j jsonNumber) encode(appendTo []byte) (e jEntry, b []byte, err error) {
|
|
decOffset := len(appendTo)
|
|
dec := apd.Decimal(j)
|
|
appendTo = encoding.EncodeUntaggedDecimalValue(appendTo, &dec)
|
|
lengthInBytes := len(appendTo) - decOffset
|
|
if err := checkLength(lengthInBytes); err != nil {
|
|
return jEntry{}, b, err
|
|
}
|
|
return makeNumberJEntry(lengthInBytes), appendTo, nil
|
|
}
|
|
|
|
// encodingModeForIdx determines which encoding mode we choose to use for a
|
|
// given i-th entry in an array or object.
|
|
func encodingModeForIdx(i int, offset uint32) encodingMode {
|
|
if i%offlenStride == 0 {
|
|
return offsetEncode(offset)
|
|
}
|
|
return lengthMode
|
|
}
|
|
|
|
func (j jsonArray) encode(appendTo []byte) (e jEntry, b []byte, err error) {
|
|
encodingStartPosition := len(appendTo)
|
|
// Array container header.
|
|
appendTo = encoding.EncodeUint32Ascending(appendTo, arrayContainerTag|uint32(len(j)))
|
|
// Reserve space for the JEntries and store where they start so we can fill them in later.
|
|
jEntryIdx := len(appendTo)
|
|
for i := 0; i < len(j); i++ {
|
|
appendTo = append(appendTo, 0, 0, 0, 0)
|
|
}
|
|
offset := uint32(0)
|
|
for i := 0; i < len(j); i++ {
|
|
var nextJEntry jEntry
|
|
nextJEntry, appendTo, err = j[i].encode(appendTo)
|
|
if err != nil {
|
|
return jEntry{}, appendTo, err
|
|
}
|
|
|
|
length := nextJEntry.length
|
|
offset += length
|
|
|
|
appendTo = encoding.PutUint32Ascending(appendTo, nextJEntry.encoded(encodingModeForIdx(i, offset)), jEntryIdx+i*4)
|
|
}
|
|
lengthInBytes := len(appendTo) - encodingStartPosition
|
|
if err := checkLength(lengthInBytes); err != nil {
|
|
return jEntry{}, b, err
|
|
}
|
|
return makeContainerJEntry(lengthInBytes), appendTo, nil
|
|
}
|
|
|
|
func (j jsonObject) encode(appendTo []byte) (e jEntry, b []byte, err error) {
|
|
encodingStartPosition := len(appendTo)
|
|
// Object container header.
|
|
appendTo = encoding.EncodeUint32Ascending(appendTo, objectContainerTag|uint32(len(j)))
|
|
// Reserve space for the key and value JEntries and store where they start so
|
|
// we can fill them in later.
|
|
jEntryIdx := len(appendTo)
|
|
for i := 0; i < len(j)*2; i++ {
|
|
appendTo = append(appendTo, 0, 0, 0, 0)
|
|
}
|
|
offset := uint32(0)
|
|
// Encode all keys.
|
|
for i := 0; i < len(j); i++ {
|
|
var nextJEntry jEntry
|
|
nextJEntry, appendTo, err = j[i].k.encode(appendTo)
|
|
if err != nil {
|
|
return jEntry{}, appendTo, err
|
|
}
|
|
|
|
length := nextJEntry.length
|
|
offset += length
|
|
|
|
appendTo = encoding.PutUint32Ascending(appendTo, nextJEntry.encoded(encodingModeForIdx(i, offset)), jEntryIdx+i*4)
|
|
}
|
|
// Encode all values.
|
|
for i := 0; i < len(j); i++ {
|
|
var nextJEntry jEntry
|
|
nextJEntry, appendTo, err = j[i].v.encode(appendTo)
|
|
if err != nil {
|
|
return jEntry{}, appendTo, err
|
|
}
|
|
|
|
length := nextJEntry.length
|
|
offset += length
|
|
|
|
appendTo = encoding.PutUint32Ascending(appendTo, nextJEntry.encoded(encodingModeForIdx(i, offset)), jEntryIdx+(len(j)+i)*4)
|
|
}
|
|
lengthInBytes := len(appendTo) - encodingStartPosition
|
|
if err := checkLength(lengthInBytes); err != nil {
|
|
return jEntry{}, b, err
|
|
}
|
|
return makeContainerJEntry(lengthInBytes), appendTo, nil
|
|
}
|
|
|
|
// EncodeJSON encodes a JSON value as a sequence of bytes.
|
|
func EncodeJSON(appendTo []byte, j JSON) ([]byte, error) {
|
|
switch j.Type() {
|
|
case ArrayJSONType, ObjectJSONType:
|
|
// We just discard the JEntry in these cases.
|
|
var err error
|
|
_, appendTo, err = j.encode(appendTo)
|
|
if err != nil {
|
|
return appendTo, err
|
|
}
|
|
return appendTo, nil
|
|
default: // j is a scalar, so we must construct a scalar container for it at the top level.
|
|
// Scalar container header.
|
|
appendTo = encoding.EncodeUint32Ascending(appendTo, scalarContainerTag)
|
|
// Reserve space for scalar jEntry.
|
|
jEntryIdx := len(appendTo)
|
|
appendTo = encoding.EncodeUint32Ascending(appendTo, 0)
|
|
var entry jEntry
|
|
var err error
|
|
entry, appendTo, err = j.encode(appendTo)
|
|
if err != nil {
|
|
return appendTo, err
|
|
}
|
|
appendTo = encoding.PutUint32Ascending(appendTo, entry.encoded(lengthMode), jEntryIdx)
|
|
return appendTo, nil
|
|
}
|
|
}
|
|
|
|
// DecodeJSON decodes a value encoded with EncodeJSON.
|
|
func DecodeJSON(b []byte) ([]byte, JSON, error) {
|
|
b, containerHeader, err := encoding.DecodeUint32Ascending(b)
|
|
if err != nil {
|
|
return b, nil, err
|
|
}
|
|
switch containerHeader & containerHeaderTypeMask {
|
|
case scalarContainerTag:
|
|
var entry jEntry
|
|
var err error
|
|
b, entry, err = decodeJEntry(b, 0)
|
|
if err != nil {
|
|
return b, nil, err
|
|
}
|
|
return decodeJSONValue(entry, b)
|
|
case arrayContainerTag:
|
|
return decodeJSONArray(containerHeader, b)
|
|
case objectContainerTag:
|
|
return decodeJSONObject(containerHeader, b)
|
|
}
|
|
return b, nil, errors.AssertionFailedf(
|
|
"error decoding JSON value, header: %x", errors.Safe(containerHeader))
|
|
}
|
|
|
|
// FromEncoding returns a JSON value which is lazily decoded.
|
|
func FromEncoding(b []byte) (JSON, error) {
|
|
return newEncodedFromRoot(b)
|
|
}
|
|
|
|
func decodeJSONArray(containerHeader uint32, b []byte) ([]byte, JSON, error) {
|
|
length := containerHeader & containerHeaderLenMask
|
|
b, jEntries, err := decodeJEntries(int(length), b)
|
|
if err != nil {
|
|
return b, nil, err
|
|
}
|
|
result := make(jsonArray, length)
|
|
for i := uint32(0); i < length; i++ {
|
|
var nextJSON JSON
|
|
b, nextJSON, err = decodeJSONValue(jEntries[i], b)
|
|
if err != nil {
|
|
return b, nil, err
|
|
}
|
|
result[i] = nextJSON
|
|
}
|
|
return b, result, nil
|
|
}
|
|
|
|
func decodeJEntries(n int, b []byte) ([]byte, []jEntry, error) {
|
|
var err error
|
|
jEntries := make([]jEntry, n)
|
|
off := uint32(0)
|
|
for i := 0; i < n; i++ {
|
|
var nextJEntry jEntry
|
|
b, nextJEntry, err = decodeJEntry(b, off)
|
|
if err != nil {
|
|
return b, nil, err
|
|
}
|
|
off += nextJEntry.length
|
|
jEntries[i] = nextJEntry
|
|
}
|
|
return b, jEntries, nil
|
|
}
|
|
|
|
func decodeJSONObject(containerHeader uint32, b []byte) ([]byte, JSON, error) {
|
|
length := int(containerHeader & containerHeaderLenMask)
|
|
|
|
b, jEntries, err := decodeJEntries(length*2, b)
|
|
if err != nil {
|
|
return b, nil, err
|
|
}
|
|
|
|
// There are `length` key entries at the start and `length` value entries at the back.
|
|
keyJEntries := jEntries[:length]
|
|
valueJEntries := jEntries[length:]
|
|
|
|
result := make(jsonObject, length)
|
|
// Decode the keys.
|
|
for i := 0; i < length; i++ {
|
|
var nextJSON JSON
|
|
b, nextJSON, err = decodeJSONValue(keyJEntries[i], b)
|
|
if err != nil {
|
|
return b, nil, err
|
|
}
|
|
if key, ok := nextJSON.(jsonString); ok {
|
|
result[i].k = key
|
|
} else {
|
|
return b, nil, errors.AssertionFailedf(
|
|
"key encoded as non-string: %T", nextJSON)
|
|
}
|
|
}
|
|
|
|
// Decode the values.
|
|
for i := 0; i < length; i++ {
|
|
var nextJSON JSON
|
|
b, nextJSON, err = decodeJSONValue(valueJEntries[i], b)
|
|
if err != nil {
|
|
return b, nil, err
|
|
}
|
|
result[i].v = nextJSON
|
|
}
|
|
return b, result, nil
|
|
}
|
|
|
|
func decodeJSONNumber(b []byte) ([]byte, JSON, error) {
|
|
b, d, err := encoding.DecodeUntaggedDecimalValue(b)
|
|
if err != nil {
|
|
return b, nil, err
|
|
}
|
|
return b, jsonNumber(d), nil
|
|
}
|
|
|
|
func decodeJSONValue(e jEntry, b []byte) ([]byte, JSON, error) {
|
|
switch e.typCode {
|
|
case trueTag:
|
|
return b, TrueJSONValue, nil
|
|
case falseTag:
|
|
return b, FalseJSONValue, nil
|
|
case nullTag:
|
|
return b, NullJSONValue, nil
|
|
case stringTag:
|
|
return b[e.length:], jsonString(b[:e.length]), nil
|
|
case numberTag:
|
|
return decodeJSONNumber(b)
|
|
case containerTag:
|
|
return DecodeJSON(b)
|
|
}
|
|
return b, nil, errors.AssertionFailedf(
|
|
"error decoding JSON value, unexpected type code: %d", errors.Safe(e.typCode))
|
|
}
|