/* 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 ( "fmt" "github.com/SAP/go-hdb/internal/bufio" ) const ( resultsetIDSize = 8 ) type columnOptions int8 const ( coMandatory columnOptions = 0x01 coOptional columnOptions = 0x02 ) var columnOptionsText = map[columnOptions]string{ coMandatory: "mandatory", coOptional: "optional", } func (k columnOptions) String() string { t := make([]string, 0, len(columnOptionsText)) for option, text := range columnOptionsText { if (k & option) != 0 { t = append(t, text) } } return fmt.Sprintf("%v", t) } //resultset id type resultsetID struct { id *uint64 } func (id *resultsetID) kind() partKind { return pkResultsetID } func (id *resultsetID) size() (int, error) { return resultsetIDSize, nil } func (id *resultsetID) numArg() int { return 1 } func (id *resultsetID) setNumArg(int) { //ignore - always 1 } func (id *resultsetID) read(rd *bufio.Reader) error { _id := rd.ReadUint64() *id.id = _id if trace { outLogger.Printf("resultset id: %d", *id.id) } return rd.GetError() } func (id *resultsetID) write(wr *bufio.Writer) error { wr.WriteUint64(*id.id) if trace { outLogger.Printf("resultset id: %d", *id.id) } return nil } // ResultFieldSet contains database field metadata for result fields. type ResultFieldSet struct { fields []*ResultField names fieldNames } func newResultFieldSet(size int) *ResultFieldSet { return &ResultFieldSet{ fields: make([]*ResultField, size), names: newFieldNames(), } } // String implements the Stringer interface. func (f *ResultFieldSet) 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 *ResultFieldSet) read(rd *bufio.Reader) { for i := 0; i < len(f.fields); i++ { field := newResultField(f.names) field.read(rd) f.fields[i] = field } pos := uint32(0) for _, offset := range f.names.sortOffsets() { if diff := int(offset - pos); diff > 0 { rd.Skip(diff) } b, size := readShortUtf8(rd) f.names.setName(offset, string(b)) pos += uint32(1 + size) } } // NumField returns the number of fields of a query. func (f *ResultFieldSet) NumField() int { return len(f.fields) } // Field returns the field at index idx. func (f *ResultFieldSet) Field(idx int) *ResultField { return f.fields[idx] } const ( tableName = iota schemaName columnName columnDisplayName maxNames ) // ResultField contains database field attributes for result fields. type ResultField struct { fieldNames fieldNames columnOptions columnOptions tc TypeCode fraction int16 length int16 offsets [maxNames]uint32 } func newResultField(fieldNames fieldNames) *ResultField { return &ResultField{fieldNames: fieldNames} } // String implements the Stringer interface. func (f *ResultField) String() string { return fmt.Sprintf("columnsOptions %s typeCode %s fraction %d length %d tablename %s schemaname %s columnname %s columnDisplayname %s", f.columnOptions, f.tc, f.fraction, f.length, f.fieldNames.name(f.offsets[tableName]), f.fieldNames.name(f.offsets[schemaName]), f.fieldNames.name(f.offsets[columnName]), f.fieldNames.name(f.offsets[columnDisplayName]), ) } // TypeCode returns the type code of the field. func (f *ResultField) TypeCode() TypeCode { return f.tc } // TypeLength returns the type length of the field. // see https://golang.org/pkg/database/sql/driver/#RowsColumnTypeLength func (f *ResultField) TypeLength() (int64, bool) { if f.tc.isVariableLength() { return int64(f.length), true } return 0, false } // TypePrecisionScale returns the type precision and scale (decimal types) of the field. // see https://golang.org/pkg/database/sql/driver/#RowsColumnTypePrecisionScale func (f *ResultField) TypePrecisionScale() (int64, int64, bool) { if f.tc.isDecimalType() { return int64(f.length), int64(f.fraction), true } return 0, 0, false } // Nullable returns true if the field may be null, false otherwise. // see https://golang.org/pkg/database/sql/driver/#RowsColumnTypeNullable func (f *ResultField) Nullable() bool { return f.columnOptions == coOptional } // Name returns the result field name. func (f *ResultField) Name() string { return f.fieldNames.name(f.offsets[columnDisplayName]) } func (f *ResultField) read(rd *bufio.Reader) { f.columnOptions = columnOptions(rd.ReadInt8()) f.tc = TypeCode(rd.ReadInt8()) f.fraction = rd.ReadInt16() f.length = rd.ReadInt16() rd.Skip(2) //filler for i := 0; i < maxNames; i++ { offset := rd.ReadUint32() f.offsets[i] = offset f.fieldNames.addOffset(offset) } } //resultset metadata type resultMetadata struct { resultFieldSet *ResultFieldSet numArg int } func (r *resultMetadata) String() string { return fmt.Sprintf("result metadata: %s", r.resultFieldSet.fields) } func (r *resultMetadata) kind() partKind { return pkResultMetadata } func (r *resultMetadata) setNumArg(numArg int) { r.numArg = numArg } func (r *resultMetadata) read(rd *bufio.Reader) error { r.resultFieldSet.read(rd) if trace { outLogger.Printf("read %s", r) } return rd.GetError() } //resultset type resultset struct { numArg int s *Session resultFieldSet *ResultFieldSet fieldValues *FieldValues } func (r *resultset) String() string { return fmt.Sprintf("resultset: %s", r.fieldValues) } func (r *resultset) kind() partKind { return pkResultset } func (r *resultset) setNumArg(numArg int) { r.numArg = numArg } func (r *resultset) read(rd *bufio.Reader) error { cols := len(r.resultFieldSet.fields) r.fieldValues.resize(r.numArg, cols) for i := 0; i < r.numArg; i++ { for j, field := range r.resultFieldSet.fields { var err error if r.fieldValues.values[i*cols+j], err = readField(r.s, rd, field.TypeCode()); err != nil { return err } } } if trace { outLogger.Printf("read %s", r) } return rd.GetError() }