mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-15 11:07:00 +02:00
* Added HANA dynamic secret backend * Added acceptance tests for HANA secret backend * Add HANA backend as a logical backend to server * Added documentation to HANA secret backend * Added vendored libraries * Go fmt * Migrate hana credential creation to plugin * Removed deprecated hana logical backend * Migrated documentation for HANA database plugin * Updated HANA DB plugin to use role name in credential generation * Update HANA plugin tests * If env vars are not configured, tests will skip rather than succeed * Fixed some improperly named string variables * Removed unused import * Import SAP hdb driver
910 lines
18 KiB
Go
910 lines
18 KiB
Go
/*
|
|
Copyright 2014 SAP SE
|
|
|
|
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 protocol
|
|
|
|
import (
|
|
"database/sql/driver"
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/SAP/go-hdb/internal/bufio"
|
|
"github.com/SAP/go-hdb/internal/unicode/cesu8"
|
|
)
|
|
|
|
const (
|
|
realNullValue uint32 = ^uint32(0)
|
|
doubleNullValue uint64 = ^uint64(0)
|
|
)
|
|
|
|
type uint32Slice []uint32
|
|
|
|
func (p uint32Slice) Len() int { return len(p) }
|
|
func (p uint32Slice) Less(i, j int) bool { return p[i] < p[j] }
|
|
func (p uint32Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
|
func (p uint32Slice) sort() { sort.Sort(p) }
|
|
|
|
type field interface {
|
|
typeCode() typeCode
|
|
in() bool
|
|
out() bool
|
|
name(map[uint32]string) string
|
|
nameOffsets() []uint32
|
|
String() string
|
|
}
|
|
|
|
// FieldSet contains database field metadata.
|
|
type FieldSet struct {
|
|
fields []field
|
|
names map[uint32]string
|
|
}
|
|
|
|
func newFieldSet(size int) *FieldSet {
|
|
return &FieldSet{
|
|
fields: make([]field, size),
|
|
names: make(map[uint32]string),
|
|
}
|
|
}
|
|
|
|
// String implements the Stringer interface.
|
|
func (f *FieldSet) String() string {
|
|
a := make([]string, len(f.fields))
|
|
for i, f := range f.fields {
|
|
a[i] = f.String()
|
|
}
|
|
return fmt.Sprintf("%v", a)
|
|
}
|
|
|
|
func (f *FieldSet) nameOffsets() []uint32 {
|
|
for _, field := range f.fields {
|
|
for _, offset := range field.nameOffsets() {
|
|
if offset != 0xFFFFFFFF {
|
|
f.names[offset] = ""
|
|
}
|
|
}
|
|
}
|
|
// sort offsets (not sure if offsets are monotonically increasing in any case)
|
|
offsets := make([]uint32, len(f.names))
|
|
i := 0
|
|
for offset := range f.names {
|
|
offsets[i] = offset
|
|
i++
|
|
}
|
|
uint32Slice(offsets).sort()
|
|
return offsets
|
|
}
|
|
|
|
// NumInputField returns the number of input fields in a database statement.
|
|
func (f *FieldSet) NumInputField() int {
|
|
cnt := 0
|
|
for _, field := range f.fields {
|
|
if field.in() {
|
|
cnt++
|
|
}
|
|
}
|
|
return cnt
|
|
}
|
|
|
|
// NumOutputField returns the number of output fields of a query or stored procedure.
|
|
func (f *FieldSet) NumOutputField() int {
|
|
cnt := 0
|
|
for _, field := range f.fields {
|
|
if field.out() {
|
|
cnt++
|
|
}
|
|
}
|
|
return cnt
|
|
}
|
|
|
|
// DataType returns the datatype of the field at index idx.
|
|
func (f *FieldSet) DataType(idx int) DataType {
|
|
return f.fields[idx].typeCode().dataType()
|
|
}
|
|
|
|
// OutputNames fills the names parameter with field names of all output fields. The size of the names slice must be at least
|
|
// NumOutputField big.
|
|
func (f *FieldSet) OutputNames(names []string) error {
|
|
i := 0
|
|
for _, field := range f.fields {
|
|
if field.out() {
|
|
if i >= len(names) { // assert names size
|
|
return fmt.Errorf("names size too short %d - expected min %d", len(names), i)
|
|
}
|
|
names[i] = field.name(f.names)
|
|
i++
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FieldValues contains rows read from database.
|
|
type FieldValues struct {
|
|
s *Session
|
|
|
|
rows int
|
|
cols int
|
|
lobCols int
|
|
values []driver.Value
|
|
|
|
descrs []*LobReadDescr // Caution: store descriptor to guarantee valid addresses
|
|
writers []lobWriter
|
|
}
|
|
|
|
func newFieldValues(s *Session) *FieldValues {
|
|
return &FieldValues{s: s}
|
|
}
|
|
|
|
func (f *FieldValues) String() string {
|
|
return fmt.Sprintf("rows %d columns %d lob columns %d", f.rows, f.cols, f.lobCols)
|
|
}
|
|
|
|
func (f *FieldValues) read(rows int, fieldSet *FieldSet, rd *bufio.Reader) error {
|
|
f.rows = rows
|
|
f.descrs = make([]*LobReadDescr, 0)
|
|
|
|
f.cols, f.lobCols = 0, 0
|
|
for _, field := range fieldSet.fields {
|
|
if field.out() {
|
|
if field.typeCode().isLob() {
|
|
f.descrs = append(f.descrs, &LobReadDescr{col: f.cols})
|
|
f.lobCols++
|
|
}
|
|
f.cols++
|
|
}
|
|
}
|
|
f.values = make([]driver.Value, f.rows*f.cols)
|
|
f.writers = make([]lobWriter, f.lobCols)
|
|
|
|
for i := 0; i < f.rows; i++ {
|
|
j := 0
|
|
for _, field := range fieldSet.fields {
|
|
|
|
if !field.out() {
|
|
continue
|
|
}
|
|
|
|
var err error
|
|
if f.values[i*f.cols+j], err = readField(rd, field.typeCode()); err != nil {
|
|
return err
|
|
}
|
|
j++
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// NumRow returns the number of rows available in FieldValues.
|
|
func (f *FieldValues) NumRow() int {
|
|
return f.rows
|
|
}
|
|
|
|
// Row fills the dest value slice with row data at index idx.
|
|
func (f *FieldValues) Row(idx int, dest []driver.Value) {
|
|
copy(dest, f.values[idx*f.cols:(idx+1)*f.cols])
|
|
|
|
if f.lobCols == 0 {
|
|
return
|
|
}
|
|
|
|
for i, descr := range f.descrs {
|
|
col := descr.col
|
|
writer := dest[col].(lobWriter)
|
|
f.writers[i] = writer
|
|
descr.w = writer
|
|
dest[col] = lobReadDescrToPointer(descr)
|
|
}
|
|
|
|
// last descriptor triggers lob read
|
|
f.descrs[f.lobCols-1].fn = func() error {
|
|
return f.s.readLobStream(f.writers)
|
|
}
|
|
}
|
|
|
|
const (
|
|
tinyintFieldSize = 1
|
|
smallintFieldSize = 2
|
|
intFieldSize = 4
|
|
bigintFieldSize = 8
|
|
realFieldSize = 4
|
|
doubleFieldSize = 8
|
|
dateFieldSize = 4
|
|
timeFieldSize = 4
|
|
timestampFieldSize = dateFieldSize + timeFieldSize
|
|
decimalFieldSize = 16
|
|
lobInputDescriptorSize = 9
|
|
)
|
|
|
|
func fieldSize(tc typeCode, v driver.Value) (int, error) {
|
|
|
|
if v == nil {
|
|
return 0, nil
|
|
}
|
|
|
|
switch tc {
|
|
case tcTinyint:
|
|
return tinyintFieldSize, nil
|
|
case tcSmallint:
|
|
return smallintFieldSize, nil
|
|
case tcInt:
|
|
return intFieldSize, nil
|
|
case tcBigint:
|
|
return bigintFieldSize, nil
|
|
case tcReal:
|
|
return realFieldSize, nil
|
|
case tcDouble:
|
|
return doubleFieldSize, nil
|
|
case tcDate:
|
|
return dateFieldSize, nil
|
|
case tcTime:
|
|
return timeFieldSize, nil
|
|
case tcTimestamp:
|
|
return timestampFieldSize, nil
|
|
case tcDecimal:
|
|
return decimalFieldSize, nil
|
|
case tcChar, tcVarchar, tcString:
|
|
switch v := v.(type) {
|
|
case []byte:
|
|
return bytesSize(len(v))
|
|
case string:
|
|
return bytesSize(len(v))
|
|
default:
|
|
outLogger.Fatalf("data type %s mismatch %T", tc, v)
|
|
}
|
|
case tcNchar, tcNvarchar, tcNstring:
|
|
switch v := v.(type) {
|
|
case []byte:
|
|
return bytesSize(cesu8.Size(v))
|
|
case string:
|
|
return bytesSize(cesu8.StringSize(v))
|
|
default:
|
|
outLogger.Fatalf("data type %s mismatch %T", tc, v)
|
|
}
|
|
case tcBinary, tcVarbinary:
|
|
v, ok := v.([]byte)
|
|
if !ok {
|
|
outLogger.Fatalf("data type %s mismatch %T", tc, v)
|
|
}
|
|
return bytesSize(len(v))
|
|
case tcBlob, tcClob, tcNclob:
|
|
return lobInputDescriptorSize, nil
|
|
}
|
|
outLogger.Fatalf("data type %s not implemented", tc)
|
|
return 0, nil
|
|
}
|
|
|
|
func readField(rd *bufio.Reader, tc typeCode) (interface{}, error) {
|
|
|
|
switch tc {
|
|
|
|
case tcTinyint, tcSmallint, tcInt, tcBigint:
|
|
|
|
valid, err := rd.ReadBool()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !valid { //null value
|
|
return nil, nil
|
|
}
|
|
|
|
switch tc {
|
|
|
|
case tcTinyint:
|
|
if v, err := rd.ReadByte(); err == nil {
|
|
return int64(v), nil
|
|
}
|
|
return nil, err
|
|
|
|
case tcSmallint:
|
|
if v, err := rd.ReadInt16(); err == nil {
|
|
return int64(v), nil
|
|
}
|
|
return nil, err
|
|
|
|
case tcInt:
|
|
if v, err := rd.ReadInt32(); err == nil {
|
|
return int64(v), nil
|
|
}
|
|
return nil, err
|
|
|
|
case tcBigint:
|
|
if v, err := rd.ReadInt64(); err == nil {
|
|
return v, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
|
|
case tcReal:
|
|
v, err := rd.ReadUint32()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if v == realNullValue {
|
|
return nil, nil
|
|
}
|
|
return float64(math.Float32frombits(v)), nil
|
|
|
|
case tcDouble:
|
|
v, err := rd.ReadUint64()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if v == doubleNullValue {
|
|
return nil, nil
|
|
}
|
|
return math.Float64frombits(v), nil
|
|
|
|
case tcDate:
|
|
|
|
year, month, day, null, err := readDate(rd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
|
|
return time.Date(year, month, day, 0, 0, 0, 0, time.UTC), nil
|
|
|
|
// time read gives only seconds (cut), no milliseconds
|
|
case tcTime:
|
|
|
|
hour, minute, nanosecs, null, err := readTime(rd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
|
|
return time.Date(1, 1, 1, hour, minute, 0, nanosecs, time.UTC), nil
|
|
|
|
case tcTimestamp:
|
|
|
|
year, month, day, dateNull, err := readDate(rd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
hour, minute, nanosecs, timeNull, err := readTime(rd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if dateNull || timeNull {
|
|
return nil, nil
|
|
}
|
|
|
|
return time.Date(year, month, day, hour, minute, 0, nanosecs, time.UTC), nil
|
|
|
|
case tcDecimal:
|
|
|
|
b, null, err := readDecimal(rd)
|
|
switch {
|
|
case err != nil:
|
|
return nil, err
|
|
case null:
|
|
return nil, nil
|
|
default:
|
|
return b, nil
|
|
}
|
|
|
|
case tcChar, tcVarchar:
|
|
value, null, err := readBytes(rd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return value, nil
|
|
|
|
case tcNchar, tcNvarchar:
|
|
value, null, err := readUtf8(rd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return value, nil
|
|
|
|
case tcBinary, tcVarbinary:
|
|
value, null, err := readBytes(rd)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return value, nil
|
|
|
|
case tcBlob, tcClob, tcNclob:
|
|
null, writer, err := readLob(rd, tc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return writer, nil
|
|
}
|
|
|
|
outLogger.Fatalf("read field: type code %s not implemented", tc)
|
|
return nil, nil
|
|
}
|
|
|
|
func writeField(wr *bufio.Writer, tc typeCode, v driver.Value) error {
|
|
|
|
// null value
|
|
if v == nil {
|
|
if err := wr.WriteByte(byte(tc) | 0x80); err != nil { //set high bit
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// type code
|
|
if err := wr.WriteByte(byte(tc)); err != nil {
|
|
return err
|
|
}
|
|
|
|
switch tc {
|
|
|
|
// TODO: char, ...
|
|
|
|
case tcTinyint, tcSmallint, tcInt, tcBigint:
|
|
|
|
i64, ok := v.(int64)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
|
|
switch tc {
|
|
case tcTinyint:
|
|
return wr.WriteByte(byte(i64))
|
|
case tcSmallint:
|
|
return wr.WriteInt16(int16(i64))
|
|
case tcInt:
|
|
return wr.WriteInt32(int32(i64))
|
|
case tcBigint:
|
|
return wr.WriteInt64(i64)
|
|
}
|
|
|
|
case tcReal:
|
|
|
|
f64, ok := v.(float64)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
return wr.WriteFloat32(float32(f64))
|
|
|
|
case tcDouble:
|
|
|
|
f64, ok := v.(float64)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
return wr.WriteFloat64(f64)
|
|
|
|
case tcDate:
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
return writeDate(wr, t)
|
|
|
|
case tcTime:
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
return writeTime(wr, t)
|
|
|
|
case tcTimestamp:
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
if err := writeDate(wr, t); err != nil {
|
|
return err
|
|
}
|
|
return writeTime(wr, t)
|
|
|
|
case tcDecimal:
|
|
b, ok := v.([]byte)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
if len(b) != 16 {
|
|
return fmt.Errorf("invalid argument length %d of type %T - expected %d", len(b), v, 16)
|
|
}
|
|
_, err := wr.Write(b)
|
|
return err
|
|
|
|
case tcChar, tcVarchar, tcString:
|
|
switch v := v.(type) {
|
|
case []byte:
|
|
return writeBytes(wr, v)
|
|
case string:
|
|
return writeString(wr, v)
|
|
default:
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
|
|
case tcNchar, tcNvarchar, tcNstring:
|
|
switch v := v.(type) {
|
|
case []byte:
|
|
return writeUtf8Bytes(wr, v)
|
|
case string:
|
|
return writeUtf8String(wr, v)
|
|
default:
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
|
|
case tcBinary, tcVarbinary:
|
|
v, ok := v.([]byte)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
return writeBytes(wr, v)
|
|
|
|
case tcBlob, tcClob, tcNclob:
|
|
return writeLob(wr)
|
|
}
|
|
|
|
outLogger.Fatalf("write field: type code %s not implemented", tc)
|
|
return nil
|
|
}
|
|
|
|
// null values: most sig bit unset
|
|
// year: unset second most sig bit (subtract 2^15)
|
|
// --> read year as unsigned
|
|
// month is 0-based
|
|
// day is 1 byte
|
|
func readDate(rd *bufio.Reader) (int, time.Month, int, bool, error) {
|
|
|
|
year, err := rd.ReadUint16()
|
|
if err != nil {
|
|
return 0, 0, 0, false, err
|
|
}
|
|
if (year & 0x8000) == 0 { //null value
|
|
if err := rd.Skip(2); err != nil {
|
|
return 0, 0, 0, false, err
|
|
}
|
|
return 0, 0, 0, true, nil
|
|
}
|
|
year &= 0x3fff
|
|
month, err := rd.ReadInt8()
|
|
if err != nil {
|
|
return 0, 0, 0, false, err
|
|
}
|
|
month++
|
|
day, err := rd.ReadInt8()
|
|
if err != nil {
|
|
return 0, 0, 0, false, err
|
|
}
|
|
return int(year), time.Month(month), int(day), false, nil
|
|
}
|
|
|
|
// year: set most sig bit
|
|
// month 0 based
|
|
func writeDate(wr *bufio.Writer, t time.Time) error {
|
|
|
|
//store in utc
|
|
utc := t.In(time.UTC)
|
|
|
|
year, month, day := utc.Date()
|
|
|
|
if err := wr.WriteUint16(uint16(year) | 0x8000); err != nil {
|
|
return err
|
|
}
|
|
if err := wr.WriteInt8(int8(month) - 1); err != nil {
|
|
return err
|
|
}
|
|
if err := wr.WriteInt8(int8(day)); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readTime(rd *bufio.Reader) (int, int, int, bool, error) {
|
|
|
|
hour, err := rd.ReadByte()
|
|
if err != nil {
|
|
return 0, 0, 0, false, err
|
|
}
|
|
if (hour & 0x80) == 0 { //null value
|
|
if err := rd.Skip(3); err != nil {
|
|
return 0, 0, 0, false, err
|
|
}
|
|
return 0, 0, 0, true, nil
|
|
}
|
|
hour &= 0x7f
|
|
minute, err := rd.ReadInt8()
|
|
if err != nil {
|
|
return 0, 0, 0, false, err
|
|
}
|
|
millisecs, err := rd.ReadUint16()
|
|
if err != nil {
|
|
return 0, 0, 0, false, err
|
|
}
|
|
|
|
nanosecs := int(millisecs) * 1000000
|
|
|
|
return int(hour), int(minute), nanosecs, false, nil
|
|
}
|
|
|
|
func writeTime(wr *bufio.Writer, t time.Time) error {
|
|
|
|
//store in utc
|
|
utc := t.UTC()
|
|
|
|
if err := wr.WriteByte(byte(utc.Hour()) | 0x80); err != nil {
|
|
return err
|
|
}
|
|
if err := wr.WriteInt8(int8(utc.Minute())); err != nil {
|
|
return err
|
|
}
|
|
|
|
millisecs := utc.Second()*1000 + utc.Round(time.Millisecond).Nanosecond()/1000000
|
|
|
|
if err := wr.WriteUint16(uint16(millisecs)); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func readDecimal(rd *bufio.Reader) ([]byte, bool, error) {
|
|
b := make([]byte, 16)
|
|
if err := rd.ReadFull(b); err != nil {
|
|
return nil, false, err
|
|
}
|
|
if (b[15] & 0x70) == 0x70 { //null value (bit 4,5,6 set)
|
|
return nil, true, nil
|
|
}
|
|
return b, false, nil
|
|
}
|
|
|
|
// string / binary length indicators
|
|
const (
|
|
bytesLenIndNullValue byte = 255
|
|
bytesLenIndSmall byte = 245
|
|
bytesLenIndMedium byte = 246
|
|
bytesLenIndBig byte = 247
|
|
)
|
|
|
|
func bytesSize(size int) (int, error) { //size + length indicator
|
|
switch {
|
|
default:
|
|
return 0, fmt.Errorf("max string length %d exceeded %d", math.MaxInt32, size)
|
|
case size <= int(bytesLenIndSmall):
|
|
return size + 1, nil
|
|
case size <= math.MaxInt16:
|
|
return size + 3, nil
|
|
case size <= math.MaxInt32:
|
|
return size + 5, nil
|
|
}
|
|
}
|
|
|
|
func readBytesSize(rd *bufio.Reader) (int, bool, error) {
|
|
|
|
ind, err := rd.ReadByte() //length indicator
|
|
if err != nil {
|
|
return 0, false, err
|
|
}
|
|
|
|
switch {
|
|
|
|
default:
|
|
return 0, false, fmt.Errorf("invalid length indicator %d", ind)
|
|
|
|
case ind == bytesLenIndNullValue:
|
|
return 0, true, nil
|
|
|
|
case ind <= bytesLenIndSmall:
|
|
return int(ind), false, nil
|
|
|
|
case ind == bytesLenIndMedium:
|
|
if size, err := rd.ReadInt16(); err == nil {
|
|
return int(size), false, nil
|
|
}
|
|
return 0, false, err
|
|
|
|
case ind == bytesLenIndBig:
|
|
if size, err := rd.ReadInt32(); err == nil {
|
|
return int(size), false, nil
|
|
}
|
|
return 0, false, err
|
|
}
|
|
}
|
|
|
|
func writeBytesSize(wr *bufio.Writer, size int) error {
|
|
switch {
|
|
|
|
default:
|
|
return fmt.Errorf("max argument length %d of string exceeded", size)
|
|
|
|
case size <= int(bytesLenIndSmall):
|
|
if err := wr.WriteByte(byte(size)); err != nil {
|
|
return err
|
|
}
|
|
case size <= math.MaxInt16:
|
|
if err := wr.WriteByte(bytesLenIndMedium); err != nil {
|
|
return err
|
|
}
|
|
if err := wr.WriteInt16(int16(size)); err != nil {
|
|
return err
|
|
}
|
|
case size <= math.MaxInt32:
|
|
if err := wr.WriteByte(bytesLenIndBig); err != nil {
|
|
return err
|
|
}
|
|
if err := wr.WriteInt32(int32(size)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readBytes(rd *bufio.Reader) ([]byte, bool, error) {
|
|
size, null, err := readBytesSize(rd)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if null {
|
|
return nil, true, nil
|
|
}
|
|
|
|
b := make([]byte, size)
|
|
if err := rd.ReadFull(b); err != nil {
|
|
return nil, false, err
|
|
}
|
|
return b, false, nil
|
|
}
|
|
|
|
func readUtf8(rd *bufio.Reader) ([]byte, bool, error) {
|
|
size, null, err := readBytesSize(rd)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if null {
|
|
return nil, true, nil
|
|
}
|
|
|
|
b, err := rd.ReadCesu8(size)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
return b, false, nil
|
|
}
|
|
|
|
// strings with one byte length
|
|
func readShortUtf8(rd *bufio.Reader) ([]byte, int, error) {
|
|
size, err := rd.ReadByte()
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
b, err := rd.ReadCesu8(int(size))
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return b, int(size), nil
|
|
}
|
|
|
|
func writeBytes(wr *bufio.Writer, b []byte) error {
|
|
if err := writeBytesSize(wr, len(b)); err != nil {
|
|
return err
|
|
}
|
|
_, err := wr.Write(b)
|
|
return err
|
|
}
|
|
|
|
func writeString(wr *bufio.Writer, s string) error {
|
|
if err := writeBytesSize(wr, len(s)); err != nil {
|
|
return err
|
|
}
|
|
_, err := wr.WriteString(s)
|
|
return err
|
|
}
|
|
|
|
func writeUtf8Bytes(wr *bufio.Writer, b []byte) error {
|
|
size := cesu8.Size(b)
|
|
if err := writeBytesSize(wr, size); err != nil {
|
|
return err
|
|
}
|
|
_, err := wr.WriteCesu8(b)
|
|
return err
|
|
}
|
|
|
|
func writeUtf8String(wr *bufio.Writer, s string) error {
|
|
size := cesu8.StringSize(s)
|
|
if err := writeBytesSize(wr, size); err != nil {
|
|
return err
|
|
}
|
|
_, err := wr.WriteStringCesu8(s)
|
|
return err
|
|
}
|
|
|
|
func readLob(rd *bufio.Reader, tc typeCode) (bool, lobWriter, error) {
|
|
|
|
if _, err := rd.ReadInt8(); err != nil { // type code (is int here)
|
|
return false, nil, err
|
|
}
|
|
|
|
opt, err := rd.ReadInt8()
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
if err := rd.Skip(2); err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
charLen, err := rd.ReadInt64()
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
byteLen, err := rd.ReadInt64()
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
id, err := rd.ReadUint64()
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
chunkLen, err := rd.ReadInt32()
|
|
if err != nil {
|
|
return false, nil, err
|
|
}
|
|
|
|
null := (lobOptions(opt) & loNullindicator) != 0
|
|
eof := (lobOptions(opt) & loLastdata) != 0
|
|
|
|
var writer lobWriter
|
|
if tc.isCharBased() {
|
|
writer = newCharLobWriter(locatorID(id), charLen, byteLen)
|
|
} else {
|
|
writer = newBinaryLobWriter(locatorID(id), charLen, byteLen)
|
|
}
|
|
if err := writer.write(rd, int(chunkLen), eof); err != nil {
|
|
return null, writer, err
|
|
}
|
|
return null, writer, nil
|
|
}
|
|
|
|
// TODO: first write: add content? - actually no data transferred
|
|
func writeLob(wr *bufio.Writer) error {
|
|
|
|
if err := wr.WriteByte(0); err != nil {
|
|
return err
|
|
}
|
|
if err := wr.WriteInt32(0); err != nil {
|
|
return err
|
|
}
|
|
if err := wr.WriteInt32(0); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|