// Copyright (C) MongoDB, Inc. 2017-present.
|
|
//
|
|
// 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
|
|
|
|
package bsonrw
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
|
|
"go.mongodb.org/mongo-driver/bson/bsontype"
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
|
|
)
|
|
|
|
// Copier is a type that allows copying between ValueReaders, ValueWriters, and
|
|
// []byte values.
|
|
type Copier struct{}
|
|
|
|
// NewCopier creates a new copier with the given registry. If a nil registry is provided
|
|
// a default registry is used.
|
|
func NewCopier() Copier {
|
|
return Copier{}
|
|
}
|
|
|
|
// CopyDocument handles copying a document from src to dst.
|
|
func CopyDocument(dst ValueWriter, src ValueReader) error {
|
|
return Copier{}.CopyDocument(dst, src)
|
|
}
|
|
|
|
// CopyDocument handles copying one document from the src to the dst.
|
|
func (c Copier) CopyDocument(dst ValueWriter, src ValueReader) error {
|
|
dr, err := src.ReadDocument()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
dw, err := dst.WriteDocument()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.copyDocumentCore(dw, dr)
|
|
}
|
|
|
|
// CopyDocumentFromBytes copies the values from a BSON document represented as a
|
|
// []byte to a ValueWriter.
|
|
func (c Copier) CopyDocumentFromBytes(dst ValueWriter, src []byte) error {
|
|
dw, err := dst.WriteDocument()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.CopyBytesToDocumentWriter(dw, src)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return dw.WriteDocumentEnd()
|
|
}
|
|
|
|
// CopyBytesToDocumentWriter copies the values from a BSON document represented as a []byte to a
|
|
// DocumentWriter.
|
|
func (c Copier) CopyBytesToDocumentWriter(dst DocumentWriter, src []byte) error {
|
|
// TODO(skriptble): Create errors types here. Anything thats a tag should be a property.
|
|
length, rem, ok := bsoncore.ReadLength(src)
|
|
if !ok {
|
|
return fmt.Errorf("couldn't read length from src, not enough bytes. length=%d", len(src))
|
|
}
|
|
if len(src) < int(length) {
|
|
return fmt.Errorf("length read exceeds number of bytes available. length=%d bytes=%d", len(src), length)
|
|
}
|
|
rem = rem[:length-4]
|
|
|
|
var t bsontype.Type
|
|
var key string
|
|
var val bsoncore.Value
|
|
for {
|
|
t, rem, ok = bsoncore.ReadType(rem)
|
|
if !ok {
|
|
return io.EOF
|
|
}
|
|
if t == bsontype.Type(0) {
|
|
if len(rem) != 0 {
|
|
return fmt.Errorf("document end byte found before end of document. remaining bytes=%v", rem)
|
|
}
|
|
break
|
|
}
|
|
|
|
key, rem, ok = bsoncore.ReadKey(rem)
|
|
if !ok {
|
|
return fmt.Errorf("invalid key found. remaining bytes=%v", rem)
|
|
}
|
|
dvw, err := dst.WriteDocumentElement(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
val, rem, ok = bsoncore.ReadValue(rem, t)
|
|
if !ok {
|
|
return fmt.Errorf("not enough bytes available to read type. bytes=%d type=%s", len(rem), t)
|
|
}
|
|
err = c.CopyValueFromBytes(dvw, t, val.Data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CopyDocumentToBytes copies an entire document from the ValueReader and
|
|
// returns it as bytes.
|
|
func (c Copier) CopyDocumentToBytes(src ValueReader) ([]byte, error) {
|
|
return c.AppendDocumentBytes(nil, src)
|
|
}
|
|
|
|
// AppendDocumentBytes functions the same as CopyDocumentToBytes, but will
|
|
// append the result to dst.
|
|
func (c Copier) AppendDocumentBytes(dst []byte, src ValueReader) ([]byte, error) {
|
|
if br, ok := src.(BytesReader); ok {
|
|
_, dst, err := br.ReadValueBytes(dst)
|
|
return dst, err
|
|
}
|
|
|
|
vw := vwPool.Get().(*valueWriter)
|
|
defer vwPool.Put(vw)
|
|
|
|
vw.reset(dst)
|
|
|
|
err := c.CopyDocument(vw, src)
|
|
dst = vw.buf
|
|
return dst, err
|
|
}
|
|
|
|
// CopyValueFromBytes will write the value represtend by t and src to dst.
|
|
func (c Copier) CopyValueFromBytes(dst ValueWriter, t bsontype.Type, src []byte) error {
|
|
if wvb, ok := dst.(BytesWriter); ok {
|
|
return wvb.WriteValueBytes(t, src)
|
|
}
|
|
|
|
vr := vrPool.Get().(*valueReader)
|
|
defer vrPool.Put(vr)
|
|
|
|
vr.reset(src)
|
|
vr.pushElement(t)
|
|
|
|
return c.CopyValue(dst, vr)
|
|
}
|
|
|
|
// CopyValueToBytes copies a value from src and returns it as a bsontype.Type and a
|
|
// []byte.
|
|
func (c Copier) CopyValueToBytes(src ValueReader) (bsontype.Type, []byte, error) {
|
|
return c.AppendValueBytes(nil, src)
|
|
}
|
|
|
|
// AppendValueBytes functions the same as CopyValueToBytes, but will append the
|
|
// result to dst.
|
|
func (c Copier) AppendValueBytes(dst []byte, src ValueReader) (bsontype.Type, []byte, error) {
|
|
if br, ok := src.(BytesReader); ok {
|
|
return br.ReadValueBytes(dst)
|
|
}
|
|
|
|
vw := vwPool.Get().(*valueWriter)
|
|
defer vwPool.Put(vw)
|
|
|
|
start := len(dst)
|
|
|
|
vw.reset(dst)
|
|
vw.push(mElement)
|
|
|
|
err := c.CopyValue(vw, src)
|
|
if err != nil {
|
|
return 0, dst, err
|
|
}
|
|
|
|
return bsontype.Type(vw.buf[start]), vw.buf[start+2:], nil
|
|
}
|
|
|
|
// CopyValue will copy a single value from src to dst.
|
|
func (c Copier) CopyValue(dst ValueWriter, src ValueReader) error {
|
|
var err error
|
|
switch src.Type() {
|
|
case bsontype.Double:
|
|
var f64 float64
|
|
f64, err = src.ReadDouble()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteDouble(f64)
|
|
case bsontype.String:
|
|
var str string
|
|
str, err = src.ReadString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
err = dst.WriteString(str)
|
|
case bsontype.EmbeddedDocument:
|
|
err = c.CopyDocument(dst, src)
|
|
case bsontype.Array:
|
|
err = c.copyArray(dst, src)
|
|
case bsontype.Binary:
|
|
var data []byte
|
|
var subtype byte
|
|
data, subtype, err = src.ReadBinary()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteBinaryWithSubtype(data, subtype)
|
|
case bsontype.Undefined:
|
|
err = src.ReadUndefined()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteUndefined()
|
|
case bsontype.ObjectID:
|
|
var oid primitive.ObjectID
|
|
oid, err = src.ReadObjectID()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteObjectID(oid)
|
|
case bsontype.Boolean:
|
|
var b bool
|
|
b, err = src.ReadBoolean()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteBoolean(b)
|
|
case bsontype.DateTime:
|
|
var dt int64
|
|
dt, err = src.ReadDateTime()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteDateTime(dt)
|
|
case bsontype.Null:
|
|
err = src.ReadNull()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteNull()
|
|
case bsontype.Regex:
|
|
var pattern, options string
|
|
pattern, options, err = src.ReadRegex()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteRegex(pattern, options)
|
|
case bsontype.DBPointer:
|
|
var ns string
|
|
var pointer primitive.ObjectID
|
|
ns, pointer, err = src.ReadDBPointer()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteDBPointer(ns, pointer)
|
|
case bsontype.JavaScript:
|
|
var js string
|
|
js, err = src.ReadJavascript()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteJavascript(js)
|
|
case bsontype.Symbol:
|
|
var symbol string
|
|
symbol, err = src.ReadSymbol()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteSymbol(symbol)
|
|
case bsontype.CodeWithScope:
|
|
var code string
|
|
var srcScope DocumentReader
|
|
code, srcScope, err = src.ReadCodeWithScope()
|
|
if err != nil {
|
|
break
|
|
}
|
|
|
|
var dstScope DocumentWriter
|
|
dstScope, err = dst.WriteCodeWithScope(code)
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = c.copyDocumentCore(dstScope, srcScope)
|
|
case bsontype.Int32:
|
|
var i32 int32
|
|
i32, err = src.ReadInt32()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteInt32(i32)
|
|
case bsontype.Timestamp:
|
|
var t, i uint32
|
|
t, i, err = src.ReadTimestamp()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteTimestamp(t, i)
|
|
case bsontype.Int64:
|
|
var i64 int64
|
|
i64, err = src.ReadInt64()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteInt64(i64)
|
|
case bsontype.Decimal128:
|
|
var d128 primitive.Decimal128
|
|
d128, err = src.ReadDecimal128()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteDecimal128(d128)
|
|
case bsontype.MinKey:
|
|
err = src.ReadMinKey()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteMinKey()
|
|
case bsontype.MaxKey:
|
|
err = src.ReadMaxKey()
|
|
if err != nil {
|
|
break
|
|
}
|
|
err = dst.WriteMaxKey()
|
|
default:
|
|
err = fmt.Errorf("Cannot copy unknown BSON type %s", src.Type())
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
func (c Copier) copyArray(dst ValueWriter, src ValueReader) error {
|
|
ar, err := src.ReadArray()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
aw, err := dst.WriteArray()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for {
|
|
vr, err := ar.ReadValue()
|
|
if err == ErrEOA {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vw, err := aw.WriteArrayElement()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.CopyValue(vw, vr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return aw.WriteArrayEnd()
|
|
}
|
|
|
|
func (c Copier) copyDocumentCore(dw DocumentWriter, dr DocumentReader) error {
|
|
for {
|
|
key, vr, err := dr.ReadElement()
|
|
if err == ErrEOD {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
vw, err := dw.WriteDocumentElement(key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = c.CopyValue(vw, vr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return dw.WriteDocumentEnd()
|
|
}
|