775 lines
17 KiB
Go
775 lines
17 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"
|
|
)
|
|
|
|
var test uint32
|
|
|
|
const (
|
|
realNullValue uint32 = ^uint32(0)
|
|
doubleNullValue uint64 = ^uint64(0)
|
|
)
|
|
|
|
const noFieldName uint32 = 0xFFFFFFFF
|
|
|
|
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 fieldNames map[uint32]string
|
|
|
|
func newFieldNames() fieldNames {
|
|
return make(map[uint32]string)
|
|
}
|
|
|
|
func (f fieldNames) addOffset(offset uint32) {
|
|
if offset != noFieldName {
|
|
f[offset] = ""
|
|
}
|
|
}
|
|
|
|
func (f fieldNames) name(offset uint32) string {
|
|
if name, ok := f[offset]; ok {
|
|
return name
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (f fieldNames) setName(offset uint32, name string) {
|
|
f[offset] = name
|
|
}
|
|
|
|
func (f fieldNames) sortOffsets() []uint32 {
|
|
offsets := make([]uint32, 0, len(f))
|
|
for k := range f {
|
|
offsets = append(offsets, k)
|
|
}
|
|
uint32Slice(offsets).sort()
|
|
return offsets
|
|
}
|
|
|
|
// FieldValues contains rows read from database.
|
|
type FieldValues struct {
|
|
rows int
|
|
cols int
|
|
values []driver.Value
|
|
}
|
|
|
|
func newFieldValues() *FieldValues {
|
|
return &FieldValues{}
|
|
}
|
|
|
|
func (f *FieldValues) String() string {
|
|
return fmt.Sprintf("rows %d columns %d", f.rows, f.cols)
|
|
}
|
|
|
|
func (f *FieldValues) resize(rows, cols int) {
|
|
f.rows, f.cols = rows, cols
|
|
f.values = make([]driver.Value, rows*cols)
|
|
}
|
|
|
|
// 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])
|
|
}
|
|
|
|
const (
|
|
tinyintFieldSize = 1
|
|
smallintFieldSize = 2
|
|
intFieldSize = 4
|
|
bigintFieldSize = 8
|
|
realFieldSize = 4
|
|
doubleFieldSize = 8
|
|
dateFieldSize = 4
|
|
timeFieldSize = 4
|
|
timestampFieldSize = dateFieldSize + timeFieldSize
|
|
longdateFieldSize = 8
|
|
seconddateFieldSize = 8
|
|
daydateFieldSize = 4
|
|
secondtimeFieldSize = 4
|
|
decimalFieldSize = 16
|
|
lobInputDescriptorSize = 9
|
|
)
|
|
|
|
func fieldSize(tc TypeCode, arg driver.NamedValue) (int, error) {
|
|
v := arg.Value
|
|
|
|
if v == nil { //HDB bug: secondtime null value --> see writeField
|
|
return 0, nil
|
|
}
|
|
|
|
switch tc {
|
|
case tcTinyint:
|
|
return tinyintFieldSize, nil
|
|
case tcSmallint:
|
|
return smallintFieldSize, nil
|
|
case tcInteger:
|
|
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 tcLongdate:
|
|
return longdateFieldSize, nil
|
|
case tcSeconddate:
|
|
return seconddateFieldSize, nil
|
|
case tcDaydate:
|
|
return daydateFieldSize, nil
|
|
case tcSecondtime:
|
|
return secondtimeFieldSize, 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(session *Session, rd *bufio.Reader, tc TypeCode) (interface{}, error) {
|
|
|
|
switch tc {
|
|
|
|
case tcTinyint, tcSmallint, tcInteger, tcBigint:
|
|
|
|
if !rd.ReadBool() { //null value
|
|
return nil, nil
|
|
}
|
|
|
|
switch tc {
|
|
case tcTinyint:
|
|
return int64(rd.ReadB()), nil
|
|
case tcSmallint:
|
|
return int64(rd.ReadInt16()), nil
|
|
case tcInteger:
|
|
return int64(rd.ReadInt32()), nil
|
|
case tcBigint:
|
|
return rd.ReadInt64(), nil
|
|
}
|
|
|
|
case tcReal:
|
|
v := rd.ReadUint32()
|
|
if v == realNullValue {
|
|
return nil, nil
|
|
}
|
|
return float64(math.Float32frombits(v)), nil
|
|
|
|
case tcDouble:
|
|
v := rd.ReadUint64()
|
|
if v == doubleNullValue {
|
|
return nil, nil
|
|
}
|
|
return math.Float64frombits(v), nil
|
|
|
|
case tcDate:
|
|
year, month, day, null := readDate(rd)
|
|
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 := readTime(rd)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return time.Date(1, 1, 1, hour, minute, 0, nanosecs, time.UTC), nil
|
|
|
|
case tcTimestamp:
|
|
year, month, day, dateNull := readDate(rd)
|
|
hour, minute, nanosecs, timeNull := readTime(rd)
|
|
if dateNull || timeNull {
|
|
return nil, nil
|
|
}
|
|
return time.Date(year, month, day, hour, minute, 0, nanosecs, time.UTC), nil
|
|
|
|
case tcLongdate:
|
|
time, null := readLongdate(rd)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return time, nil
|
|
|
|
case tcSeconddate:
|
|
time, null := readSeconddate(rd)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return time, nil
|
|
|
|
case tcDaydate:
|
|
time, null := readDaydate(rd)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return time, nil
|
|
|
|
case tcSecondtime:
|
|
time, null := readSecondtime(rd)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return time, nil
|
|
|
|
case tcDecimal:
|
|
b, null := readDecimal(rd)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return b, nil
|
|
|
|
case tcChar, tcVarchar:
|
|
value, null := readBytes(rd)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return value, nil
|
|
|
|
case tcNchar, tcNvarchar:
|
|
value, null := readUtf8(rd)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return value, nil
|
|
|
|
case tcBinary, tcVarbinary:
|
|
value, null := readBytes(rd)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return value, nil
|
|
|
|
case tcBlob, tcClob, tcNclob:
|
|
null, writer, err := readLob(session, rd, tc)
|
|
if null {
|
|
return nil, nil
|
|
}
|
|
return writer, err
|
|
}
|
|
|
|
outLogger.Fatalf("read field: type code %s not implemented", tc)
|
|
return nil, nil
|
|
}
|
|
|
|
func writeField(wr *bufio.Writer, tc TypeCode, arg driver.NamedValue) error {
|
|
v := arg.Value
|
|
//HDB bug: secondtime null value cannot be set by setting high byte
|
|
// trying so, gives
|
|
// SQL HdbError 1033 - error while parsing protocol: no such data type: type_code=192, index=2
|
|
|
|
// null value
|
|
//if v == nil && tc != tcSecondtime
|
|
if v == nil {
|
|
wr.WriteB(byte(tc) | 0x80) //set high bit
|
|
return nil
|
|
}
|
|
|
|
// type code
|
|
wr.WriteB(byte(tc))
|
|
|
|
switch tc {
|
|
|
|
default:
|
|
outLogger.Fatalf("write field: type code %s not implemented", tc)
|
|
|
|
case tcTinyint, tcSmallint, tcInteger, tcBigint:
|
|
var i64 int64
|
|
|
|
switch v := v.(type) {
|
|
default:
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
|
|
case bool:
|
|
if v {
|
|
i64 = 1
|
|
} else {
|
|
i64 = 0
|
|
}
|
|
case int64:
|
|
i64 = v
|
|
}
|
|
|
|
switch tc {
|
|
case tcTinyint:
|
|
wr.WriteB(byte(i64))
|
|
case tcSmallint:
|
|
wr.WriteInt16(int16(i64))
|
|
case tcInteger:
|
|
wr.WriteInt32(int32(i64))
|
|
case tcBigint:
|
|
wr.WriteInt64(i64)
|
|
}
|
|
|
|
case tcReal:
|
|
|
|
f64, ok := v.(float64)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
wr.WriteFloat32(float32(f64))
|
|
|
|
case tcDouble:
|
|
|
|
f64, ok := v.(float64)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
wr.WriteFloat64(f64)
|
|
|
|
case tcDate:
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
writeDate(wr, t)
|
|
|
|
case tcTime:
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
writeTime(wr, t)
|
|
|
|
case tcTimestamp:
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
writeDate(wr, t)
|
|
writeTime(wr, t)
|
|
|
|
case tcLongdate:
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
writeLongdate(wr, t)
|
|
|
|
case tcSeconddate:
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
writeSeconddate(wr, t)
|
|
|
|
case tcDaydate:
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
writeDaydate(wr, t)
|
|
|
|
case tcSecondtime:
|
|
// HDB bug: write null value explicite
|
|
if v == nil {
|
|
wr.WriteInt32(86401)
|
|
return nil
|
|
}
|
|
t, ok := v.(time.Time)
|
|
if !ok {
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
writeSecondtime(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)
|
|
}
|
|
wr.Write(b)
|
|
|
|
case tcChar, tcVarchar, tcString:
|
|
switch v := v.(type) {
|
|
case []byte:
|
|
writeBytes(wr, v)
|
|
case string:
|
|
writeString(wr, v)
|
|
default:
|
|
return fmt.Errorf("invalid argument type %T", v)
|
|
}
|
|
|
|
case tcNchar, tcNvarchar, tcNstring:
|
|
switch v := v.(type) {
|
|
case []byte:
|
|
writeUtf8Bytes(wr, v)
|
|
case string:
|
|
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)
|
|
}
|
|
writeBytes(wr, v)
|
|
|
|
case tcBlob, tcClob, tcNclob:
|
|
writeLob(wr)
|
|
}
|
|
|
|
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) {
|
|
year := rd.ReadUint16()
|
|
null := ((year & 0x8000) == 0) //null value
|
|
year &= 0x3fff
|
|
month := rd.ReadInt8()
|
|
month++
|
|
day := rd.ReadInt8()
|
|
return int(year), time.Month(month), int(day), null
|
|
}
|
|
|
|
// year: set most sig bit
|
|
// month 0 based
|
|
func writeDate(wr *bufio.Writer, t time.Time) {
|
|
//store in utc
|
|
utc := t.In(time.UTC)
|
|
|
|
year, month, day := utc.Date()
|
|
|
|
wr.WriteUint16(uint16(year) | 0x8000)
|
|
wr.WriteInt8(int8(month) - 1)
|
|
wr.WriteInt8(int8(day))
|
|
}
|
|
|
|
func readTime(rd *bufio.Reader) (int, int, int, bool) {
|
|
hour := rd.ReadB()
|
|
null := (hour & 0x80) == 0 //null value
|
|
hour &= 0x7f
|
|
minute := rd.ReadInt8()
|
|
millisecs := rd.ReadUint16()
|
|
nanosecs := int(millisecs) * 1000000
|
|
return int(hour), int(minute), nanosecs, null
|
|
}
|
|
|
|
func writeTime(wr *bufio.Writer, t time.Time) {
|
|
//store in utc
|
|
utc := t.UTC()
|
|
|
|
wr.WriteB(byte(utc.Hour()) | 0x80)
|
|
wr.WriteInt8(int8(utc.Minute()))
|
|
millisecs := utc.Second()*1000 + utc.Round(time.Millisecond).Nanosecond()/1000000
|
|
wr.WriteUint16(uint16(millisecs))
|
|
}
|
|
|
|
var zeroTime = time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC)
|
|
|
|
func readLongdate(rd *bufio.Reader) (time.Time, bool) {
|
|
longdate := rd.ReadInt64()
|
|
if longdate == 3155380704000000001 { // null value
|
|
return zeroTime, true
|
|
}
|
|
return convertLongdateToTime(longdate), false
|
|
}
|
|
|
|
func writeLongdate(wr *bufio.Writer, t time.Time) {
|
|
wr.WriteInt64(convertTimeToLongdate(t))
|
|
}
|
|
|
|
func readSeconddate(rd *bufio.Reader) (time.Time, bool) {
|
|
seconddate := rd.ReadInt64()
|
|
if seconddate == 315538070401 { // null value
|
|
return zeroTime, true
|
|
}
|
|
return convertSeconddateToTime(seconddate), false
|
|
}
|
|
|
|
func writeSeconddate(wr *bufio.Writer, t time.Time) {
|
|
wr.WriteInt64(convertTimeToSeconddate(t))
|
|
}
|
|
|
|
func readDaydate(rd *bufio.Reader) (time.Time, bool) {
|
|
daydate := rd.ReadInt32()
|
|
if daydate == 3652062 { // null value
|
|
return zeroTime, true
|
|
}
|
|
return convertDaydateToTime(int64(daydate)), false
|
|
}
|
|
|
|
func writeDaydate(wr *bufio.Writer, t time.Time) {
|
|
wr.WriteInt32(int32(convertTimeToDayDate(t)))
|
|
}
|
|
|
|
func readSecondtime(rd *bufio.Reader) (time.Time, bool) {
|
|
secondtime := rd.ReadInt32()
|
|
if secondtime == 86401 { // null value
|
|
return zeroTime, true
|
|
}
|
|
return convertSecondtimeToTime(int(secondtime)), false
|
|
}
|
|
|
|
func writeSecondtime(wr *bufio.Writer, t time.Time) {
|
|
wr.WriteInt32(int32(convertTimeToSecondtime(t)))
|
|
}
|
|
|
|
// nanosecond: HDB - 7 digits precision (not 9 digits)
|
|
func convertTimeToLongdate(t time.Time) int64 {
|
|
t = t.UTC()
|
|
return (((((((int64(convertTimeToDayDate(t))-1)*24)+int64(t.Hour()))*60)+int64(t.Minute()))*60)+int64(t.Second()))*10000000 + int64(t.Nanosecond()/100) + 1
|
|
}
|
|
|
|
func convertLongdateToTime(longdate int64) time.Time {
|
|
const dayfactor = 10000000 * 24 * 60 * 60
|
|
longdate--
|
|
d := (longdate % dayfactor) * 100
|
|
t := convertDaydateToTime((longdate / dayfactor) + 1)
|
|
return t.Add(time.Duration(d))
|
|
}
|
|
|
|
func convertTimeToSeconddate(t time.Time) int64 {
|
|
t = t.UTC()
|
|
return (((((int64(convertTimeToDayDate(t))-1)*24)+int64(t.Hour()))*60)+int64(t.Minute()))*60 + int64(t.Second()) + 1
|
|
}
|
|
|
|
func convertSeconddateToTime(seconddate int64) time.Time {
|
|
const dayfactor = 24 * 60 * 60
|
|
seconddate--
|
|
d := (seconddate % dayfactor) * 1000000000
|
|
t := convertDaydateToTime((seconddate / dayfactor) + 1)
|
|
return t.Add(time.Duration(d))
|
|
}
|
|
|
|
const julianHdb = 1721423 // 1 January 0001 00:00:00 (1721424) - 1
|
|
|
|
func convertTimeToDayDate(t time.Time) int64 {
|
|
return int64(timeToJulianDay(t) - julianHdb)
|
|
}
|
|
|
|
func convertDaydateToTime(daydate int64) time.Time {
|
|
return julianDayToTime(int(daydate) + julianHdb)
|
|
}
|
|
|
|
func convertTimeToSecondtime(t time.Time) int {
|
|
t = t.UTC()
|
|
return (t.Hour()*60+t.Minute())*60 + t.Second() + 1
|
|
}
|
|
|
|
func convertSecondtimeToTime(secondtime int) time.Time {
|
|
return time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC).Add(time.Duration(int64(secondtime-1) * 1000000000))
|
|
}
|
|
|
|
func readDecimal(rd *bufio.Reader) ([]byte, bool) {
|
|
b := make([]byte, 16)
|
|
rd.ReadFull(b)
|
|
if (b[15] & 0x70) == 0x70 { //null value (bit 4,5,6 set)
|
|
return nil, true
|
|
}
|
|
return b, false
|
|
}
|
|
|
|
// 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) {
|
|
|
|
ind := rd.ReadB() //length indicator
|
|
|
|
switch {
|
|
|
|
default:
|
|
return 0, false
|
|
|
|
case ind == bytesLenIndNullValue:
|
|
return 0, true
|
|
|
|
case ind <= bytesLenIndSmall:
|
|
return int(ind), false
|
|
|
|
case ind == bytesLenIndMedium:
|
|
return int(rd.ReadInt16()), false
|
|
|
|
case ind == bytesLenIndBig:
|
|
return int(rd.ReadInt32()), false
|
|
|
|
}
|
|
}
|
|
|
|
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):
|
|
wr.WriteB(byte(size))
|
|
case size <= math.MaxInt16:
|
|
wr.WriteB(bytesLenIndMedium)
|
|
wr.WriteInt16(int16(size))
|
|
case size <= math.MaxInt32:
|
|
wr.WriteB(bytesLenIndBig)
|
|
wr.WriteInt32(int32(size))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readBytes(rd *bufio.Reader) ([]byte, bool) {
|
|
size, null := readBytesSize(rd)
|
|
if null {
|
|
return nil, true
|
|
}
|
|
b := make([]byte, size)
|
|
rd.ReadFull(b)
|
|
return b, false
|
|
}
|
|
|
|
func readUtf8(rd *bufio.Reader) ([]byte, bool) {
|
|
size, null := readBytesSize(rd)
|
|
if null {
|
|
return nil, true
|
|
}
|
|
b := rd.ReadCesu8(size)
|
|
return b, false
|
|
}
|
|
|
|
// strings with one byte length
|
|
func readShortUtf8(rd *bufio.Reader) ([]byte, int) {
|
|
size := rd.ReadB()
|
|
b := rd.ReadCesu8(int(size))
|
|
return b, int(size)
|
|
}
|
|
|
|
func writeBytes(wr *bufio.Writer, b []byte) {
|
|
writeBytesSize(wr, len(b))
|
|
wr.Write(b)
|
|
}
|
|
|
|
func writeString(wr *bufio.Writer, s string) {
|
|
writeBytesSize(wr, len(s))
|
|
wr.WriteString(s)
|
|
}
|
|
|
|
func writeUtf8Bytes(wr *bufio.Writer, b []byte) {
|
|
size := cesu8.Size(b)
|
|
writeBytesSize(wr, size)
|
|
wr.WriteCesu8(b)
|
|
}
|
|
|
|
func writeUtf8String(wr *bufio.Writer, s string) {
|
|
size := cesu8.StringSize(s)
|
|
writeBytesSize(wr, size)
|
|
wr.WriteStringCesu8(s)
|
|
}
|
|
|
|
func readLob(s *Session, rd *bufio.Reader, tc TypeCode) (bool, lobChunkWriter, error) {
|
|
rd.ReadInt8() // type code (is int here)
|
|
opt := rd.ReadInt8()
|
|
null := (lobOptions(opt) & loNullindicator) != 0
|
|
if null {
|
|
return true, nil, nil
|
|
}
|
|
eof := (lobOptions(opt) & loLastdata) != 0
|
|
rd.Skip(2)
|
|
|
|
charLen := rd.ReadInt64()
|
|
byteLen := rd.ReadInt64()
|
|
id := rd.ReadUint64()
|
|
chunkLen := rd.ReadInt32()
|
|
|
|
lobChunkWriter := newLobChunkWriter(tc.isCharBased(), s, locatorID(id), charLen, byteLen)
|
|
if err := lobChunkWriter.write(rd, int(chunkLen), eof); err != nil {
|
|
return null, lobChunkWriter, err
|
|
}
|
|
return null, lobChunkWriter, nil
|
|
}
|
|
|
|
// TODO: first write: add content? - actually no data transferred
|
|
func writeLob(wr *bufio.Writer) {
|
|
wr.WriteB(0)
|
|
wr.WriteInt32(0)
|
|
wr.WriteInt32(0)
|
|
}
|