mirror of
https://github.com/mainflux/mainflux.git
synced 2025-05-02 22:17:10 +08:00

* Add mongodb reader service Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com> * Add tests for mongodb reader service Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com> * Add documentation for mongodb reader service Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com> * Fix test function name Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com> * Update comment in docker-compose for mongodb-reader service Signed-off-by: Aleksandar Novakovic <aleksandar.novakovic@mainflux.com>
1036 lines
29 KiB
Go
1036 lines
29 KiB
Go
// 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 bson
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/mongodb/mongo-go-driver/bson/decimal"
|
|
"github.com/mongodb/mongo-go-driver/bson/objectid"
|
|
)
|
|
|
|
// Value represents a BSON value. It can be obtained as part of a bson.Element or created for use
|
|
// in a bson.Array with the bson.VC constructors.
|
|
type Value struct {
|
|
// NOTE: For subdocuments, arrays, and code with scope, the data slice of
|
|
// bytes may contain just the key, or the key and the code in the case of
|
|
// code with scope. If this is the case, the start will be 0, the value will
|
|
// be the length of the slice, and d will be non-nil.
|
|
|
|
// start is the offset into the data slice of bytes where this element
|
|
// begins.
|
|
start uint32
|
|
// offset is the offset into the data slice of bytes where this element's
|
|
// value begins.
|
|
offset uint32
|
|
// data is a potentially shared slice of bytes that contains the actual
|
|
// element. Most of the methods of this type directly index into this slice
|
|
// of bytes.
|
|
data []byte
|
|
|
|
d *Document
|
|
}
|
|
|
|
// Offset returns the offset to the beginning of the value in the underlying data. When called on
|
|
// a value obtained from a Reader, it can be used to find the value manually within the Reader's
|
|
// bytes.
|
|
func (v *Value) Offset() uint32 {
|
|
return v.offset
|
|
}
|
|
|
|
// Interface returns the Go value of this Value as an empty interface.
|
|
func (v *Value) Interface() interface{} {
|
|
if v == nil {
|
|
return nil
|
|
}
|
|
|
|
switch v.Type() {
|
|
case TypeDouble:
|
|
return v.Double()
|
|
case TypeString:
|
|
return v.StringValue()
|
|
case TypeEmbeddedDocument:
|
|
return v.ReaderDocument().String()
|
|
case TypeArray:
|
|
return v.MutableArray().String()
|
|
case TypeBinary:
|
|
_, data := v.Binary()
|
|
return data
|
|
case TypeUndefined:
|
|
return nil
|
|
case TypeObjectID:
|
|
return v.ObjectID()
|
|
case TypeBoolean:
|
|
return v.Boolean()
|
|
case TypeDateTime:
|
|
return v.DateTime()
|
|
case TypeNull:
|
|
return nil
|
|
case TypeRegex:
|
|
p, o := v.Regex()
|
|
return Regex{Pattern: p, Options: o}
|
|
case TypeDBPointer:
|
|
db, pointer := v.DBPointer()
|
|
return DBPointer{DB: db, Pointer: pointer}
|
|
case TypeJavaScript:
|
|
return v.JavaScript()
|
|
case TypeSymbol:
|
|
return v.Symbol()
|
|
case TypeCodeWithScope:
|
|
code, scope := v.MutableJavaScriptWithScope()
|
|
return CodeWithScope{Code: code, Scope: scope}
|
|
case TypeInt32:
|
|
return v.Int32()
|
|
case TypeTimestamp:
|
|
t, i := v.Timestamp()
|
|
return Timestamp{T: t, I: i}
|
|
case TypeInt64:
|
|
return v.Int64()
|
|
case TypeDecimal128:
|
|
return v.Decimal128()
|
|
case TypeMinKey:
|
|
return nil
|
|
case TypeMaxKey:
|
|
return nil
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (v *Value) validate(sizeOnly bool) (uint32, error) {
|
|
if v.data == nil {
|
|
return 0, ErrUninitializedElement
|
|
}
|
|
|
|
var total uint32
|
|
|
|
switch v.data[v.start] {
|
|
case '\x06', '\x0A', '\xFF', '\x7F':
|
|
case '\x01':
|
|
if int(v.offset+8) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += 8
|
|
case '\x02', '\x0D', '\x0E':
|
|
if int(v.offset+4) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
total += 4
|
|
if int32(v.offset)+4+l > int32(len(v.data)) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
// We check if the value that is the last element of the string is a
|
|
// null terminator. We take the value offset, add 4 to account for the
|
|
// length, add the length of the string, and subtract one since the size
|
|
// isn't zero indexed.
|
|
if !sizeOnly && v.data[v.offset+4+uint32(l)-1] != 0x00 {
|
|
return total, ErrInvalidString
|
|
}
|
|
total += uint32(l)
|
|
case '\x03':
|
|
if v.d != nil {
|
|
n, err := v.d.Validate()
|
|
total += uint32(n)
|
|
if err != nil {
|
|
return total, err
|
|
}
|
|
break
|
|
}
|
|
|
|
if int(v.offset+4) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
total += 4
|
|
if l < 5 {
|
|
return total, ErrInvalidReadOnlyDocument
|
|
}
|
|
if int32(v.offset)+l > int32(len(v.data)) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
if !sizeOnly {
|
|
n, err := Reader(v.data[v.offset : v.offset+uint32(l)]).Validate()
|
|
total += n - 4
|
|
if err != nil {
|
|
return total, err
|
|
}
|
|
break
|
|
}
|
|
total += uint32(l) - 4
|
|
case '\x04':
|
|
if v.d != nil {
|
|
n, err := (&Array{v.d}).Validate()
|
|
total += uint32(n)
|
|
if err != nil {
|
|
return total, err
|
|
}
|
|
break
|
|
}
|
|
|
|
if int(v.offset+4) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
total += 4
|
|
if l < 5 {
|
|
return total, ErrInvalidReadOnlyDocument
|
|
}
|
|
if int32(v.offset)+l > int32(len(v.data)) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
if !sizeOnly {
|
|
n, err := Reader(v.data[v.offset : v.offset+uint32(l)]).Validate()
|
|
total += n - 4
|
|
if err != nil {
|
|
return total, err
|
|
}
|
|
break
|
|
}
|
|
total += uint32(l) - 4
|
|
case '\x05':
|
|
if int(v.offset+5) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
total += 5
|
|
if v.data[v.offset+4] > '\x05' && v.data[v.offset+4] < '\x80' {
|
|
return total, ErrInvalidBinarySubtype
|
|
}
|
|
if int32(v.offset)+5+l > int32(len(v.data)) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += uint32(l)
|
|
case '\x07':
|
|
if int(v.offset+12) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += 12
|
|
case '\x08':
|
|
if int(v.offset+1) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total++
|
|
if v.data[v.offset] != '\x00' && v.data[v.offset] != '\x01' {
|
|
return total, ErrInvalidBooleanType
|
|
}
|
|
case '\x09':
|
|
if int(v.offset+8) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += 8
|
|
case '\x0B':
|
|
i := v.offset
|
|
for ; int(i) < len(v.data) && v.data[i] != '\x00'; i++ {
|
|
total++
|
|
}
|
|
if int(i) == len(v.data) || v.data[i] != '\x00' {
|
|
return total, ErrInvalidString
|
|
}
|
|
i++
|
|
total++
|
|
for ; int(i) < len(v.data) && v.data[i] != '\x00'; i++ {
|
|
total++
|
|
}
|
|
if int(i) == len(v.data) || v.data[i] != '\x00' {
|
|
return total, ErrInvalidString
|
|
}
|
|
total++
|
|
case '\x0C':
|
|
if int(v.offset+4) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
total += 4
|
|
if int32(v.offset)+4+l+12 > int32(len(v.data)) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += uint32(l) + 12
|
|
case '\x0F':
|
|
if v.d != nil {
|
|
// NOTE: For code with scope specifically, we write the length as
|
|
// we are marshaling the element and the constructor doesn't know
|
|
// the length of the document when it constructs the element.
|
|
// Because of that we don't check the length here and just validate
|
|
// the string and the document.
|
|
if int(v.offset+8) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += 8
|
|
sLength := readi32(v.data[v.offset+4 : v.offset+8])
|
|
if int(sLength) > len(v.data)+8 {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += uint32(sLength)
|
|
if !sizeOnly && v.data[v.offset+8+uint32(sLength)-1] != 0x00 {
|
|
return total, ErrInvalidString
|
|
}
|
|
|
|
n, err := v.d.Validate()
|
|
total += uint32(n)
|
|
if err != nil {
|
|
return total, err
|
|
}
|
|
break
|
|
}
|
|
if int(v.offset+4) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
total += 4
|
|
if int32(v.offset)+l > int32(len(v.data)) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
if !sizeOnly {
|
|
sLength := readi32(v.data[v.offset+4 : v.offset+8])
|
|
total += 4
|
|
// If the length of the string is larger than the total length of the
|
|
// field minus the int32 for length, 5 bytes for a minimum document
|
|
// size, and an int32 for the string length the value is invalid.
|
|
//
|
|
// TODO(skriptble): We should actually validate that the string
|
|
// doesn't consume any of the bytes used by the document.
|
|
if sLength > l-13 {
|
|
return total, ErrStringLargerThanContainer
|
|
}
|
|
// We check if the value that is the last element of the string is a
|
|
// null terminator. We take the value offset, add 4 to account for the
|
|
// length, add the length of the string, and subtract one since the size
|
|
// isn't zero indexed.
|
|
if v.data[v.offset+8+uint32(sLength)-1] != 0x00 {
|
|
return total, ErrInvalidString
|
|
}
|
|
total += uint32(sLength)
|
|
n, err := Reader(v.data[v.offset+8+uint32(sLength) : v.offset+uint32(l)]).Validate()
|
|
total += n
|
|
if err != nil {
|
|
return total, err
|
|
}
|
|
break
|
|
}
|
|
total += uint32(l) - 4
|
|
case '\x10':
|
|
if int(v.offset+4) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += 4
|
|
case '\x11', '\x12':
|
|
if int(v.offset+8) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += 8
|
|
case '\x13':
|
|
if int(v.offset+16) > len(v.data) {
|
|
return total, NewErrTooSmall()
|
|
}
|
|
total += 16
|
|
|
|
default:
|
|
return total, ErrInvalidElement
|
|
}
|
|
|
|
return total, nil
|
|
}
|
|
|
|
// valueSize returns the size of the value in bytes.
|
|
func (v *Value) valueSize() (uint32, error) {
|
|
return v.validate(true)
|
|
}
|
|
|
|
// Type returns the identifying element byte for this element.
|
|
// It panics if e is uninitialized.
|
|
func (v *Value) Type() Type {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
return Type(v.data[v.start])
|
|
}
|
|
|
|
// IsNumber returns true if the type of v is a numberic BSON type.
|
|
func (v *Value) IsNumber() bool {
|
|
switch v.Type() {
|
|
case TypeDouble, TypeInt32, TypeInt64, TypeDecimal128:
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Double returns the float64 value for this element.
|
|
// It panics if e's BSON type is not double ('\x01') or if e is uninitialized.
|
|
func (v *Value) Double() float64 {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x01' {
|
|
panic(ElementTypeError{"compact.Element.double", Type(v.data[v.start])})
|
|
}
|
|
return math.Float64frombits(v.getUint64())
|
|
}
|
|
|
|
// DoubleOK is the same as Double, but returns a boolean instead of panicking.
|
|
func (v *Value) DoubleOK() (float64, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeDouble {
|
|
return 0, false
|
|
}
|
|
return v.Double(), true
|
|
}
|
|
|
|
// StringValue returns the string balue for this element.
|
|
// It panics if e's BSON type is not StringValue ('\x02') or if e is uninitialized.
|
|
//
|
|
// NOTE: This method is called StringValue to avoid it implementing the
|
|
// fmt.Stringer interface.
|
|
func (v *Value) StringValue() string {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x02' {
|
|
panic(ElementTypeError{"compact.Element.String", Type(v.data[v.start])})
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
return string(v.data[v.offset+4 : int32(v.offset)+4+l-1])
|
|
}
|
|
|
|
// StringValueOK is the same as StringValue, but returns a boolean instead of
|
|
// panicking.
|
|
func (v *Value) StringValueOK() (string, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeString {
|
|
return "", false
|
|
}
|
|
return v.StringValue(), true
|
|
}
|
|
|
|
// ReaderDocument returns the BSON document the Value represents as a bson.Reader. It panics if the
|
|
// value is a BSON type other than document.
|
|
func (v *Value) ReaderDocument() Reader {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
|
|
if v.data[v.start] != '\x03' {
|
|
panic(ElementTypeError{"compact.Element.Document", Type(v.data[v.start])})
|
|
}
|
|
|
|
var r Reader
|
|
if v.d == nil {
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
r = Reader(v.data[v.offset : v.offset+uint32(l)])
|
|
} else {
|
|
scope, err := v.d.MarshalBSON()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
r = Reader(scope)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
// ReaderDocumentOK is the same as ReaderDocument, except it returns a boolean
|
|
// instead of panicking.
|
|
func (v *Value) ReaderDocumentOK() (Reader, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeEmbeddedDocument {
|
|
return nil, false
|
|
}
|
|
return v.ReaderDocument(), true
|
|
}
|
|
|
|
// MutableDocument returns the subdocument for this element.
|
|
func (v *Value) MutableDocument() *Document {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x03' {
|
|
panic(ElementTypeError{"compact.Element.Document", Type(v.data[v.start])})
|
|
}
|
|
if v.d == nil {
|
|
var err error
|
|
l := int32(binary.LittleEndian.Uint32(v.data[v.offset : v.offset+4]))
|
|
v.d, err = ReadDocument(v.data[v.offset : v.offset+uint32(l)])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return v.d
|
|
}
|
|
|
|
// MutableDocumentOK is the same as MutableDocument, except it returns a boolean
|
|
// instead of panicking.
|
|
func (v *Value) MutableDocumentOK() (*Document, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeEmbeddedDocument {
|
|
return nil, false
|
|
}
|
|
return v.MutableDocument(), true
|
|
}
|
|
|
|
// ReaderArray returns the BSON document the Value represents as a bson.Reader. It panics if the
|
|
// value is a BSON type other than array.
|
|
func (v *Value) ReaderArray() Reader {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
|
|
if v.data[v.start] != '\x04' {
|
|
panic(ElementTypeError{"compact.Element.Array", Type(v.data[v.start])})
|
|
}
|
|
|
|
var r Reader
|
|
if v.d == nil {
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
r = Reader(v.data[v.offset : v.offset+uint32(l)])
|
|
} else {
|
|
scope, err := v.d.MarshalBSON()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
r = Reader(scope)
|
|
}
|
|
|
|
return r
|
|
}
|
|
|
|
// ReaderArrayOK is the same as ReaderArray, except it returns a boolean instead
|
|
// of panicking.
|
|
func (v *Value) ReaderArrayOK() (Reader, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeArray {
|
|
return nil, false
|
|
}
|
|
return v.ReaderArray(), true
|
|
}
|
|
|
|
// MutableArray returns the array for this element.
|
|
func (v *Value) MutableArray() *Array {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x04' {
|
|
panic(ElementTypeError{"compact.Element.Array", Type(v.data[v.start])})
|
|
}
|
|
if v.d == nil {
|
|
var err error
|
|
l := int32(binary.LittleEndian.Uint32(v.data[v.offset : v.offset+4]))
|
|
v.d, err = ReadDocument(v.data[v.offset : v.offset+uint32(l)])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return &Array{v.d}
|
|
}
|
|
|
|
// MutableArrayOK is the same as MutableArray, except it returns a boolean
|
|
// instead of panicking.
|
|
func (v *Value) MutableArrayOK() (*Array, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeArray {
|
|
return nil, false
|
|
}
|
|
return v.MutableArray(), true
|
|
}
|
|
|
|
// Binary returns the BSON binary value the Value represents. It panics if the value is a BSON type
|
|
// other than binary.
|
|
func (v *Value) Binary() (subtype byte, data []byte) {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x05' {
|
|
panic(ElementTypeError{"compact.Element.binary", Type(v.data[v.start])})
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
st := v.data[v.offset+4]
|
|
b := make([]byte, l)
|
|
copy(b, v.data[v.offset+5:int32(v.offset)+5+l])
|
|
return st, b
|
|
}
|
|
|
|
// ObjectID returns the BSON objectid value the Value represents. It panics if the value is a BSON
|
|
// type other than objectid.
|
|
func (v *Value) ObjectID() objectid.ObjectID {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x07' {
|
|
panic(ElementTypeError{"compact.Element.ObejctID", Type(v.data[v.start])})
|
|
}
|
|
var arr [12]byte
|
|
copy(arr[:], v.data[v.offset:v.offset+12])
|
|
return arr
|
|
}
|
|
|
|
// ObjectIDOK is the same as ObjectID, except it returns a boolean instead of
|
|
// panicking.
|
|
func (v *Value) ObjectIDOK() (objectid.ObjectID, bool) {
|
|
var empty objectid.ObjectID
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeObjectID {
|
|
return empty, false
|
|
}
|
|
return v.ObjectID(), true
|
|
}
|
|
|
|
// Boolean returns the boolean value the Value represents. It panics if the
|
|
// value is a BSON type other than boolean.
|
|
func (v *Value) Boolean() bool {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x08' {
|
|
panic(ElementTypeError{"compact.Element.Boolean", Type(v.data[v.start])})
|
|
}
|
|
return v.data[v.offset] == '\x01'
|
|
}
|
|
|
|
// BooleanOK is the same as Boolean, except it returns a boolean instead of
|
|
// panicking.
|
|
func (v *Value) BooleanOK() (bool, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeBoolean {
|
|
return false, false
|
|
}
|
|
return v.Boolean(), true
|
|
}
|
|
|
|
// DateTime returns the BSON datetime value the Value represents. It panics if the value is a BSON
|
|
// type other than datetime.
|
|
func (v *Value) DateTime() time.Time {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x09' {
|
|
panic(ElementTypeError{"compact.Element.dateTime", Type(v.data[v.start])})
|
|
}
|
|
i := v.getUint64()
|
|
return time.Unix(int64(i)/1000, int64(i)%1000*1000000)
|
|
}
|
|
|
|
// Regex returns the BSON regex value the Value represents. It panics if the value is a BSON
|
|
// type other than regex.
|
|
func (v *Value) Regex() (pattern, options string) {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x0B' {
|
|
panic(ElementTypeError{"compact.Element.regex", Type(v.data[v.start])})
|
|
}
|
|
// TODO(skriptble): Use the elements package here.
|
|
var pstart, pend, ostart, oend uint32
|
|
i := v.offset
|
|
pstart = i
|
|
for ; v.data[i] != '\x00'; i++ {
|
|
}
|
|
pend = i
|
|
i++
|
|
ostart = i
|
|
for ; v.data[i] != '\x00'; i++ {
|
|
}
|
|
oend = i
|
|
|
|
return string(v.data[pstart:pend]), string(v.data[ostart:oend])
|
|
}
|
|
|
|
// DateTimeOK is the same as DateTime, except it returns a boolean instead of
|
|
// panicking.
|
|
func (v *Value) DateTimeOK() (time.Time, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeDateTime {
|
|
return time.Time{}, false
|
|
}
|
|
return v.DateTime(), true
|
|
}
|
|
|
|
// DBPointer returns the BSON dbpointer value the Value represents. It panics if the value is a BSON
|
|
// type other than DBPointer.
|
|
func (v *Value) DBPointer() (string, objectid.ObjectID) {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x0C' {
|
|
panic(ElementTypeError{"compact.Element.dbPointer", Type(v.data[v.start])})
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
var p [12]byte
|
|
copy(p[:], v.data[v.offset+4+uint32(l):v.offset+4+uint32(l)+12])
|
|
return string(v.data[v.offset+4 : int32(v.offset)+4+l-1]), p
|
|
}
|
|
|
|
// DBPointerOK is the same as DBPoitner, except that it returns a boolean
|
|
// instead of panicking.
|
|
func (v *Value) DBPointerOK() (string, objectid.ObjectID, bool) {
|
|
var empty objectid.ObjectID
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeDBPointer {
|
|
return "", empty, false
|
|
}
|
|
s, o := v.DBPointer()
|
|
return s, o, true
|
|
}
|
|
|
|
// JavaScript returns the BSON JavaScript code value the Value represents. It panics if the value is
|
|
// a BSON type other than JavaScript code.
|
|
func (v *Value) JavaScript() string {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x0D' {
|
|
panic(ElementTypeError{"compact.Element.JavaScript", Type(v.data[v.start])})
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
return string(v.data[v.offset+4 : int32(v.offset)+4+l-1])
|
|
}
|
|
|
|
// JavaScriptOK is the same as Javascript, excepti that it returns a boolean
|
|
// instead of panicking.
|
|
func (v *Value) JavaScriptOK() (string, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeJavaScript {
|
|
return "", false
|
|
}
|
|
return v.JavaScript(), true
|
|
}
|
|
|
|
// Symbol returns the BSON symbol value the Value represents. It panics if the value is a BSON
|
|
// type other than symbol.
|
|
func (v *Value) Symbol() string {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x0E' {
|
|
panic(ElementTypeError{"compact.Element.symbol", Type(v.data[v.start])})
|
|
}
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
return string(v.data[v.offset+4 : int32(v.offset)+4+l-1])
|
|
}
|
|
|
|
// ReaderJavaScriptWithScope returns the BSON JavaScript code with scope the Value represents, with
|
|
// the scope being returned as a bson.Reader. It panics if the value is a BSON type other than
|
|
// JavaScript code with scope.
|
|
func (v *Value) ReaderJavaScriptWithScope() (string, Reader) {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
|
|
if v.data[v.start] != '\x0F' {
|
|
panic(ElementTypeError{"compact.Element.JavaScriptWithScope", Type(v.data[v.start])})
|
|
}
|
|
|
|
sLength := readi32(v.data[v.offset+4 : v.offset+8])
|
|
// If the length of the string is larger than the total length of the
|
|
// field minus the int32 for length, 5 bytes for a minimum document
|
|
// size, and an int32 for the string length the value is invalid.
|
|
str := string(v.data[v.offset+8 : v.offset+8+uint32(sLength)-1])
|
|
|
|
var r Reader
|
|
if v.d == nil {
|
|
l := readi32(v.data[v.offset : v.offset+4])
|
|
r = Reader(v.data[v.offset+8+uint32(sLength) : v.offset+uint32(l)])
|
|
} else {
|
|
scope, err := v.d.MarshalBSON()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
r = Reader(scope)
|
|
}
|
|
|
|
return str, r
|
|
}
|
|
|
|
// ReaderJavaScriptWithScopeOK is the same as ReaderJavaScriptWithScope,
|
|
// except that it returns a boolean instead of panicking.
|
|
func (v *Value) ReaderJavaScriptWithScopeOK() (string, Reader, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeCodeWithScope {
|
|
return "", nil, false
|
|
}
|
|
s, r := v.ReaderJavaScriptWithScope()
|
|
return s, r, true
|
|
}
|
|
|
|
// MutableJavaScriptWithScope returns the javascript code and the scope document for
|
|
// this element.
|
|
func (v *Value) MutableJavaScriptWithScope() (code string, d *Document) {
|
|
if v == nil || v.offset == 0 {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x0F' {
|
|
panic(ElementTypeError{"compact.Element.JavaScriptWithScope", Type(v.data[v.start])})
|
|
}
|
|
// TODO(skriptble): This is wrong and could cause a panic.
|
|
l := int32(binary.LittleEndian.Uint32(v.data[v.offset : v.offset+4]))
|
|
// TODO(skriptble): This is wrong and could cause a panic.
|
|
sLength := int32(binary.LittleEndian.Uint32(v.data[v.offset+4 : v.offset+8]))
|
|
// If the length of the string is larger than the total length of the
|
|
// field minus the int32 for length, 5 bytes for a minimum document
|
|
// size, and an int32 for the string length the value is invalid.
|
|
str := string(v.data[v.offset+4 : v.offset+4+uint32(sLength)])
|
|
if v.d == nil {
|
|
var err error
|
|
v.d, err = ReadDocument(v.data[v.offset+4+uint32(sLength) : v.offset+uint32(l)])
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
return str, v.d
|
|
}
|
|
|
|
// MutableJavaScriptWithScopeOK is the same as MutableJavascriptWithScope,
|
|
// except that it returns a boolean instead of panicking.
|
|
func (v *Value) MutableJavaScriptWithScopeOK() (string, *Document, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeCodeWithScope {
|
|
return "", nil, false
|
|
}
|
|
s, d := v.MutableJavaScriptWithScope()
|
|
return s, d, true
|
|
}
|
|
|
|
// Int32 returns the int32 the Value represents. It panics if the value is a BSON type other than
|
|
// int32.
|
|
func (v *Value) Int32() int32 {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x10' {
|
|
panic(ElementTypeError{"compact.Element.int32", Type(v.data[v.start])})
|
|
}
|
|
return readi32(v.data[v.offset : v.offset+4])
|
|
}
|
|
|
|
// Int32OK is the same as Int32, except that it returns a boolean instead of
|
|
// panicking.
|
|
func (v *Value) Int32OK() (int32, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeInt32 {
|
|
return 0, false
|
|
}
|
|
return v.Int32(), true
|
|
}
|
|
|
|
// Timestamp returns the BSON timestamp value the Value represents. It panics if the value is a
|
|
// BSON type other than timestamp.
|
|
func (v *Value) Timestamp() (uint32, uint32) {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x11' {
|
|
panic(ElementTypeError{"compact.Element.timestamp", Type(v.data[v.start])})
|
|
}
|
|
return binary.LittleEndian.Uint32(v.data[v.offset+4 : v.offset+8]), binary.LittleEndian.Uint32(v.data[v.offset : v.offset+4])
|
|
}
|
|
|
|
// TimestampOK is the same as Timestamp, except that it returns a boolean
|
|
// instead of panicking.
|
|
func (v *Value) TimestampOK() (uint32, uint32, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeTimestamp {
|
|
return 0, 0, false
|
|
}
|
|
t, i := v.Timestamp()
|
|
return t, i, true
|
|
}
|
|
|
|
// Int64 returns the int64 the Value represents. It panics if the value is a BSON type other than
|
|
// int64.
|
|
func (v *Value) Int64() int64 {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x12' {
|
|
panic(ElementTypeError{"compact.Element.int64Type", Type(v.data[v.start])})
|
|
}
|
|
return int64(v.getUint64())
|
|
}
|
|
|
|
func (v *Value) getUint64() uint64 {
|
|
return binary.LittleEndian.Uint64(v.data[v.offset : v.offset+8])
|
|
}
|
|
|
|
// Int64OK is the same as Int64, except that it returns a boolean instead of
|
|
// panicking.
|
|
func (v *Value) Int64OK() (int64, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeInt64 {
|
|
return 0, false
|
|
}
|
|
return v.Int64(), true
|
|
}
|
|
|
|
// Decimal128 returns the decimal the Value represents. It panics if the value is a BSON type other than
|
|
// decimal.
|
|
func (v *Value) Decimal128() decimal.Decimal128 {
|
|
if v == nil || v.offset == 0 || v.data == nil {
|
|
panic(ErrUninitializedElement)
|
|
}
|
|
if v.data[v.start] != '\x13' {
|
|
panic(ElementTypeError{"compact.Element.Decimal128", Type(v.data[v.start])})
|
|
}
|
|
l := binary.LittleEndian.Uint64(v.data[v.offset : v.offset+8])
|
|
h := binary.LittleEndian.Uint64(v.data[v.offset+8 : v.offset+16])
|
|
return decimal.NewDecimal128(h, l)
|
|
}
|
|
|
|
// Decimal128OK is the same as Decimal128, except that it returns a boolean
|
|
// instead of panicking.
|
|
func (v *Value) Decimal128OK() (decimal.Decimal128, bool) {
|
|
if v == nil || v.offset == 0 || v.data == nil || Type(v.data[v.start]) != TypeDecimal128 {
|
|
return decimal.NewDecimal128(0, 0), false
|
|
}
|
|
return v.Decimal128(), true
|
|
}
|
|
|
|
func (v *Value) asString() (string, error) {
|
|
var str string
|
|
var err error
|
|
switch v.Type() {
|
|
case TypeString:
|
|
str = v.StringValue()
|
|
case TypeDouble:
|
|
str = fmt.Sprintf("%f", v.Double())
|
|
case TypeInt32:
|
|
str = fmt.Sprintf("%d", v.Int32())
|
|
case TypeInt64:
|
|
str = fmt.Sprintf("%d", v.Int64())
|
|
case TypeBoolean:
|
|
str = fmt.Sprintf("%t", v.Boolean())
|
|
case TypeNull:
|
|
str = "null"
|
|
default:
|
|
err = fmt.Errorf("cannot Stringify %s yet", v.Type())
|
|
}
|
|
return str, err
|
|
}
|
|
|
|
func (v *Value) setString(str string) {
|
|
size := 2 + 4 + len(str) + 1
|
|
b := make([]byte, size)
|
|
b[0], b[1] = byte(TypeString), 0x00
|
|
copy(b[2:], str)
|
|
b[size-1] = 0x00
|
|
|
|
v.start = 0
|
|
v.offset = 2
|
|
v.data = b
|
|
}
|
|
|
|
func (v *Value) setDouble(f float64) {
|
|
header := v.data[v.start:v.offset]
|
|
if v.start != 0 || len(v.data) < len(header)+8 {
|
|
b := make([]byte, len(header)+8)
|
|
copy(b, header)
|
|
v.offset = v.offset - v.start
|
|
v.start = 0
|
|
v.data = b
|
|
}
|
|
v.data[v.start] = byte(TypeDouble)
|
|
bits := math.Float64bits(f)
|
|
binary.LittleEndian.PutUint64(v.data[v.offset:v.offset+8], bits)
|
|
}
|
|
|
|
func (v *Value) setInt32(i int32) {
|
|
header := v.data[v.start:v.offset]
|
|
if v.start != 0 || len(v.data) < len(header)+4 {
|
|
b := make([]byte, len(header)+4)
|
|
copy(b, header)
|
|
v.offset = v.offset - v.start
|
|
v.start = 0
|
|
v.data = b
|
|
}
|
|
v.data[v.start] = byte(TypeInt32)
|
|
binary.LittleEndian.PutUint32(v.data[v.offset:v.offset+4], uint32(i))
|
|
}
|
|
|
|
func (v *Value) setInt64(i int64) {
|
|
header := v.data[v.start:v.offset]
|
|
if v.start != 0 || len(v.data) < len(header)+8 {
|
|
b := make([]byte, len(header)+8)
|
|
copy(b, header)
|
|
v.offset = v.offset - v.start
|
|
v.start = 0
|
|
v.data = b
|
|
}
|
|
v.data[v.start] = byte(TypeInt64)
|
|
binary.LittleEndian.PutUint64(v.data[v.offset:v.offset+8], uint64(i))
|
|
}
|
|
|
|
func (v *Value) addNumber(v2 *Value) {
|
|
// TODO: decimal128
|
|
switch v.Type() {
|
|
case TypeDouble:
|
|
switch v2.Type() {
|
|
case TypeDouble:
|
|
v.setDouble(v.Double() + v2.Double())
|
|
case TypeInt32:
|
|
v.setDouble(v.Double() + float64(v2.Int32()))
|
|
case TypeInt64:
|
|
v.setDouble(v.Double() + float64(v2.Int64()))
|
|
}
|
|
case TypeInt32:
|
|
switch v2.Type() {
|
|
case TypeDouble:
|
|
v.setDouble(float64(v.Int32()) + v2.Double())
|
|
case TypeInt32:
|
|
v.setInt32(v.Int32() + v2.Int32())
|
|
case TypeInt64:
|
|
v.setInt64(int64(v.Int32()) + v2.Int64())
|
|
}
|
|
case TypeInt64:
|
|
switch v2.Type() {
|
|
case TypeDouble:
|
|
v.setDouble(float64(v.Int64()) + v.Double())
|
|
case TypeInt32:
|
|
v.setInt64(v.Int64() + int64(v2.Int32()))
|
|
case TypeInt64:
|
|
v.setInt64(v.Int64() + v2.Int64())
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add will add this Value to another. This is currently only implemented for
|
|
// strings and numbers. If either value is a string, the other type is coerced
|
|
// into a string and added to the other.
|
|
func (v *Value) Add(v2 *Value) error {
|
|
if v.Type() == TypeString || v2.Type() == TypeString {
|
|
str1, err := v.asString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
str2, err := v2.asString()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
v.setString(str1 + str2)
|
|
return nil
|
|
}
|
|
|
|
if v.IsNumber() && v2.IsNumber() {
|
|
v.addNumber(v2)
|
|
return nil
|
|
}
|
|
|
|
return fmt.Errorf("cannot Add values of types %s and %s yet", v.Type(), v2.Type())
|
|
}
|
|
|
|
func (v *Value) equal(v2 *Value) bool {
|
|
if v == nil && v2 == nil {
|
|
return true
|
|
}
|
|
|
|
if v == nil || v2 == nil {
|
|
return false
|
|
}
|
|
|
|
if v.start != v2.start {
|
|
return false
|
|
}
|
|
|
|
if v.offset != v2.offset {
|
|
return false
|
|
}
|
|
|
|
if v.d != nil && !v.d.Equal(v2.d) {
|
|
return false
|
|
}
|
|
|
|
return bytes.Equal(v.data, v2.data)
|
|
}
|