169 lines
4.2 KiB
Go
169 lines
4.2 KiB
Go
// Copyright 2017 PingCAP, Inc.
|
|
//
|
|
// 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,
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package distsql
|
|
|
|
import (
|
|
"github.com/juju/errors"
|
|
"github.com/pingcap/tidb/context"
|
|
"github.com/pingcap/tidb/kv"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/chunk"
|
|
"github.com/pingcap/tidb/util/codec"
|
|
"github.com/pingcap/tipb/go-tipb"
|
|
goctx "golang.org/x/net/context"
|
|
)
|
|
|
|
// streamResult implements the SelectResult interface.
|
|
type streamResult struct {
|
|
resp kv.Response
|
|
rowLen int
|
|
fieldTypes []*types.FieldType
|
|
ctx context.Context
|
|
|
|
// NOTE: curr == nil means stream finish, while len(curr.RowsData) == 0 doesn't.
|
|
curr *tipb.Chunk
|
|
scanCount int64
|
|
}
|
|
|
|
func (r *streamResult) ScanCount() int64 {
|
|
return r.scanCount
|
|
}
|
|
|
|
func (r *streamResult) Fetch(goctx.Context) {}
|
|
|
|
func (r *streamResult) Next(goctx.Context) (PartialResult, error) {
|
|
var ret streamPartialResult
|
|
ret.rowLen = r.rowLen
|
|
finished, err := r.readDataFromResponse(r.resp, &ret.Chunk)
|
|
if err != nil || finished {
|
|
return nil, errors.Trace(err)
|
|
}
|
|
return &ret, nil
|
|
}
|
|
|
|
func (r *streamResult) NextChunk(goCtx goctx.Context, chk *chunk.Chunk) error {
|
|
chk.Reset()
|
|
maxChunkSize := r.ctx.GetSessionVars().MaxChunkSize
|
|
for chk.NumRows() < maxChunkSize {
|
|
err := r.readDataIfNecessary()
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
if r.curr == nil {
|
|
return nil
|
|
}
|
|
|
|
err = r.flushToChunk(chk)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// readDataFromResponse read the data to result. Returns true means the resp is finished.
|
|
func (r *streamResult) readDataFromResponse(resp kv.Response, result *tipb.Chunk) (bool, error) {
|
|
data, err := resp.Next()
|
|
if err != nil {
|
|
return false, errors.Trace(err)
|
|
}
|
|
if data == nil {
|
|
return true, nil
|
|
}
|
|
|
|
var stream tipb.StreamResponse
|
|
err = stream.Unmarshal(data)
|
|
if err != nil {
|
|
return false, errors.Trace(err)
|
|
}
|
|
if stream.Error != nil {
|
|
return false, errors.Errorf("stream response error: [%d]%s\n", stream.Error.Code, stream.Error.Msg)
|
|
}
|
|
|
|
// TODO: Check stream.GetEncodeType() here if we support tipb.EncodeType_TypeArrow some day.
|
|
|
|
err = result.Unmarshal(stream.Data)
|
|
if err != nil {
|
|
return false, errors.Trace(err)
|
|
}
|
|
if len(stream.OutputCounts) > 0 {
|
|
r.scanCount += stream.OutputCounts[0]
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// readDataIfNecessary ensures there are some data in current chunk. If no more data, r.curr == nil.
|
|
func (r *streamResult) readDataIfNecessary() error {
|
|
if r.curr != nil && len(r.curr.RowsData) > 0 {
|
|
return nil
|
|
}
|
|
|
|
tmp := new(tipb.Chunk)
|
|
finish, err := r.readDataFromResponse(r.resp, tmp)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
if finish {
|
|
r.curr = nil
|
|
return nil
|
|
}
|
|
r.curr = tmp
|
|
return nil
|
|
}
|
|
|
|
func (r *streamResult) flushToChunk(chk *chunk.Chunk) (err error) {
|
|
remainRowsData := r.curr.RowsData
|
|
maxChunkSize := r.ctx.GetSessionVars().MaxChunkSize
|
|
timeZone := r.ctx.GetSessionVars().GetTimeZone()
|
|
for chk.NumRows() < maxChunkSize && len(remainRowsData) > 0 {
|
|
for i := 0; i < r.rowLen; i++ {
|
|
remainRowsData, err = codec.DecodeOneToChunk(remainRowsData, chk, i, r.fieldTypes[i], timeZone)
|
|
if err != nil {
|
|
return errors.Trace(err)
|
|
}
|
|
}
|
|
}
|
|
if len(remainRowsData) == 0 {
|
|
r.curr = nil // Current chunk is finished.
|
|
} else {
|
|
r.curr.RowsData = remainRowsData
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *streamResult) NextRaw() ([]byte, error) {
|
|
return r.resp.Next()
|
|
}
|
|
|
|
func (r *streamResult) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// streamPartialResult implements PartialResult.
|
|
type streamPartialResult struct {
|
|
tipb.Chunk
|
|
rowLen int
|
|
}
|
|
|
|
func (pr *streamPartialResult) Next(goCtx goctx.Context) (data []types.Datum, err error) {
|
|
if len(pr.Chunk.RowsData) == 0 {
|
|
return nil, nil // partial result finished.
|
|
}
|
|
return readRowFromChunk(&pr.Chunk, pr.rowLen)
|
|
}
|
|
|
|
func (pr *streamPartialResult) Close() error {
|
|
return nil
|
|
}
|