diff --git a/server/handler.go b/server/handler.go index 9f15da4678..53647a8fcf 100644 --- a/server/handler.go +++ b/server/handler.go @@ -701,7 +701,10 @@ func (h *Handler) resultForDefaultIter(ctx *sql.Context, c *mysql.Conn, schema s defer wg.Done() for { if r == nil { - r = &sqltypes.Result{Fields: resultFields} + r = &sqltypes.Result{ + Fields: resultFields, + Rows: make([][]sqltypes.Value, 0, rowsBatch), + } } if r.RowsAffected == rowsBatch { if err := resetCallback(r, more); err != nil { diff --git a/sql/expression/function/aggregation/group_concat.go b/sql/expression/function/aggregation/group_concat.go index ceca8a0ca9..2c48e15a88 100644 --- a/sql/expression/function/aggregation/group_concat.go +++ b/sql/expression/function/aggregation/group_concat.go @@ -308,7 +308,7 @@ func (g *groupConcatBuffer) Update(ctx *sql.Context, originalRow sql.Row) error // Append the current value to the end of the row. We want to preserve the row's original structure // for sort ordering in the final step. - g.rows = append(g.rows, append(originalRow, vs)) + g.rows = append(g.rows, append(originalRow.Copy(), vs)) return nil } diff --git a/sql/expression/function/aggregation/window_partition.go b/sql/expression/function/aggregation/window_partition.go index 9bf15e8dd9..88ba356b25 100644 --- a/sql/expression/function/aggregation/window_partition.go +++ b/sql/expression/function/aggregation/window_partition.go @@ -170,7 +170,7 @@ func (i *WindowPartitionIter) materializeInput(ctx *sql.Context) (sql.WindowBuff } return nil, nil, err } - input = append(input, append(row, j)) + input = append(input, append(append(sql.Row(nil), row...), j)) j++ } diff --git a/sql/rowexec/ddl_iters.go b/sql/rowexec/ddl_iters.go index becf91167e..25f4d43695 100644 --- a/sql/rowexec/ddl_iters.go +++ b/sql/rowexec/ddl_iters.go @@ -918,7 +918,7 @@ func (i *loggingKeyValueIter) Close(ctx *sql.Context) error { // projectRowWithTypes projects the row given with the projections given and additionally converts them to the // corresponding types found in the schema given, using the standard type conversion logic. func projectRowWithTypes(ctx *sql.Context, oldSchema, newSchema sql.Schema, projections []sql.Expression, r sql.Row) (sql.Row, error) { - newRow, err := ProjectRow(ctx, projections, r) + newRow, err := ProjectRow(ctx, projections, r, nil) if err != nil { return nil, err } @@ -1440,7 +1440,7 @@ func (i *addColumnIter) rewriteTable(ctx *sql.Context, rwt sql.RewritableTable) return false, err } - newRow, err := ProjectRow(ctx, projections, r) + newRow, err := ProjectRow(ctx, projections, r, nil) if err != nil { _ = inserter.DiscardChanges(ctx, err) _ = inserter.Close(ctx) @@ -1736,7 +1736,7 @@ func (i *dropColumnIter) rewriteTable(ctx *sql.Context, rwt sql.RewritableTable) return false, err } - newRow, err := ProjectRow(ctx, projections, r) + newRow, err := ProjectRow(ctx, projections, r, nil) if err != nil { _ = inserter.DiscardChanges(ctx, err) _ = inserter.Close(ctx) @@ -2240,7 +2240,7 @@ func buildIndex(ctx *sql.Context, n *plan.AlterIndex, ibt sql.IndexBuildingTable } if isVirtual { - r, err = ProjectRow(ctx, projections, r) + r, err = ProjectRow(ctx, projections, r, nil) if err != nil { return err } @@ -2326,7 +2326,7 @@ func rewriteTableForIndexCreate(ctx *sql.Context, n *plan.AlterIndex, table sql. } if isVirtual { - r, err = ProjectRow(ctx, projections, r) + r, err = ProjectRow(ctx, projections, r, nil) if err != nil { return err } diff --git a/sql/rowexec/insert.go b/sql/rowexec/insert.go index 70238f4ce5..8032f6134f 100644 --- a/sql/rowexec/insert.go +++ b/sql/rowexec/insert.go @@ -67,15 +67,16 @@ func getInsertExpressions(values sql.Node) []sql.Expression { } func (i *insertIter) Next(ctx *sql.Context) (returnRow sql.Row, returnErr error) { - row, err := i.rowSource.Next(ctx) + origRow, err := i.rowSource.Next(ctx) if err == io.EOF { return nil, err } - if err != nil { - return nil, i.ignoreOrClose(ctx, row, err) + return nil, i.ignoreOrClose(ctx, origRow, err) } + row := origRow.Copy() + // Increment row number for error reporting (MySQL starts at 1) i.rowNumber++ @@ -107,9 +108,6 @@ func (i *insertIter) Next(ctx *sql.Context) (returnRow sql.Row, returnErr error) return nil, i.ignoreOrClose(ctx, row, err) } - origRow := make(sql.Row, len(row)) - copy(origRow, row) - // Do any necessary type conversions to the target schema for idx, col := range i.schema { if row[idx] != nil { diff --git a/sql/rowexec/join_iters.go b/sql/rowexec/join_iters.go index 30d7de4100..8849bd976e 100644 --- a/sql/rowexec/join_iters.go +++ b/sql/rowexec/join_iters.go @@ -48,6 +48,8 @@ type joinIter struct { rowSize int scopeLen int parentLen int + + rowBuffer *sql.RowBuffer } func newJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, row sql.Row) (sql.RowIter, error) { @@ -75,9 +77,10 @@ func newJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, row return nil, err } - parentLen := len(row) + rowBuffer := sql.RowBufPool.Get().(*sql.RowBuffer) - primaryRow := make(sql.Row, parentLen+len(j.Left().Schema())) + parentLen := len(row) + primaryRow := rowBuffer.Get(parentLen + len(j.Left().Schema())) copy(primaryRow, row) return sql.NewSpanIter(span, &joinIter{ @@ -94,6 +97,8 @@ func newJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, row rowSize: parentLen + len(j.Left().Schema()) + len(j.Right().Schema()), scopeLen: j.ScopeLen, parentLen: parentLen, + + rowBuffer: rowBuffer, }), nil } @@ -184,6 +189,8 @@ func (i *joinIter) Next(ctx *sql.Context) (sql.Row, error) { } if !sql.IsTrue(res) { + // TODO: we are trashing row here, so we can release the memory...right? + i.rowBuffer.Erase(i.rowSize) continue } @@ -200,13 +207,16 @@ func (i *joinIter) removeParentRow(r sql.Row) sql.Row { // buildRow builds the result set row using the rows from the primary and secondary tables func (i *joinIter) buildRow(primary, secondary sql.Row) sql.Row { - row := make(sql.Row, i.rowSize) + row := i.rowBuffer.Get(i.rowSize) copy(row, primary) copy(row[len(primary):], secondary) return row } func (i *joinIter) Close(ctx *sql.Context) (err error) { + //i.rowBuffer.Reset() + //sql.RowBufPool.Put(i.rowBuffer) + if i.primary != nil { if err = i.primary.Close(ctx); err != nil { if i.secondary != nil { @@ -232,11 +242,13 @@ func newExistsIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, ro parentLen := len(row) + rowBuffer := sql.RowBufPool.Get().(*sql.RowBuffer) + rowSize := parentLen + len(j.Left().Schema()) + len(j.Right().Schema()) - fullRow := make(sql.Row, rowSize) + fullRow := rowBuffer.Get(rowSize) copy(fullRow, row) - primaryRow := make(sql.Row, parentLen+len(j.Left().Schema())) + primaryRow := rowBuffer.Get(parentLen + len(j.Left().Schema())) copy(primaryRow, row) return &existsIter{ @@ -251,6 +263,7 @@ func newExistsIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, ro scopeLen: j.ScopeLen, rowSize: rowSize, nullRej: !(j.Filter != nil && plan.IsNullRejecting(j.Filter)), + rowBuffer: rowBuffer, }, nil } @@ -271,6 +284,8 @@ type existsIter struct { nullRej bool rightIterNonEmpty bool + + rowBuffer *sql.RowBuffer } type existsState uint8 @@ -396,13 +411,16 @@ func (i *existsIter) removeParentRow(r sql.Row) sql.Row { // buildRow builds the result set row using the rows from the primary and secondary tables func (i *existsIter) buildRow(primary, secondary sql.Row) sql.Row { - row := make(sql.Row, i.rowSize) + row := i.rowBuffer.Get(i.rowSize) copy(row, primary) copy(row[len(primary):], secondary) return row } func (i *existsIter) Close(ctx *sql.Context) (err error) { + i.rowBuffer.Reset() + sql.RowBufPool.Put(i.rowBuffer) + if i.primary != nil { if err = i.primary.Close(ctx); err != nil { return err @@ -411,26 +429,6 @@ func (i *existsIter) Close(ctx *sql.Context) (err error) { return err } -func newFullJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, row sql.Row) (sql.RowIter, error) { - leftIter, err := b.Build(ctx, j.Left(), row) - if err != nil { - return nil, err - } - return &fullJoinIter{ - parentRow: row, - l: leftIter, - rp: j.Right(), - cond: j.Filter, - scopeLen: j.ScopeLen, - rowSize: len(row) + len(j.Left().Schema()) + len(j.Right().Schema()), - seenLeft: make(map[uint64]struct{}), - seenRight: make(map[uint64]struct{}), - leftLen: len(j.Left().Schema()), - rightLen: len(j.Right().Schema()), - b: b, - }, nil -} - // fullJoinIter implements full join as a union of left and right join: // FJ(A,B) => U(LJ(A,B), RJ(A,B)). The current algorithm will have a // runtime and memory complexity O(m+n). @@ -451,6 +449,30 @@ type fullJoinIter struct { leftDone bool seenLeft map[uint64]struct{} seenRight map[uint64]struct{} + + rowBuffer *sql.RowBuffer +} + +func newFullJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, row sql.Row) (sql.RowIter, error) { + leftIter, err := b.Build(ctx, j.Left(), row) + if err != nil { + return nil, err + } + return &fullJoinIter{ + parentRow: row, + l: leftIter, + rp: j.Right(), + cond: j.Filter, + scopeLen: j.ScopeLen, + rowSize: len(row) + len(j.Left().Schema()) + len(j.Right().Schema()), + seenLeft: make(map[uint64]struct{}), + seenRight: make(map[uint64]struct{}), + leftLen: len(j.Left().Schema()), + rightLen: len(j.Right().Schema()), + b: b, + + rowBuffer: sql.RowBufPool.Get().(*sql.RowBuffer), + }, nil } func (i *fullJoinIter) Next(ctx *sql.Context) (sql.Row, error) { @@ -546,7 +568,7 @@ func (i *fullJoinIter) Next(ctx *sql.Context) (sql.Row, error) { continue } // (null, right) only if we haven't matched right - ret := make(sql.Row, i.rowSize) + ret := i.rowBuffer.Get(i.rowSize) copy(ret[i.leftLen:], rightRow) return i.removeParentRow(ret), nil } @@ -560,13 +582,16 @@ func (i *fullJoinIter) removeParentRow(r sql.Row) sql.Row { // buildRow builds the result set row using the rows from the primary and secondary tables func (i *fullJoinIter) buildRow(primary, secondary sql.Row) sql.Row { - row := make(sql.Row, i.rowSize) + row := i.rowBuffer.Get(i.rowSize) copy(row, primary) copy(row[len(primary):], secondary) return row } func (i *fullJoinIter) Close(ctx *sql.Context) (err error) { + i.rowBuffer.Reset() + sql.RowBufPool.Put(i.rowBuffer) + if i.l != nil { err = i.l.Close(ctx) } @@ -593,6 +618,8 @@ type crossJoinIterator struct { rowSize int scopeLen int parentLen int + + rowBuffer *sql.RowBuffer } func newCrossJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, row sql.Row) (sql.RowIter, error) { @@ -620,9 +647,10 @@ func newCrossJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, return nil, err } - parentLen := len(row) + rowBuffer := sql.RowBufPool.Get().(*sql.RowBuffer) - primaryRow := make(sql.Row, parentLen+len(j.Left().Schema())) + parentLen := len(row) + primaryRow := rowBuffer.Get(parentLen + len(j.Left().Schema())) copy(primaryRow, row) return sql.NewSpanIter(span, &crossJoinIterator{ @@ -635,6 +663,8 @@ func newCrossJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, rowSize: len(row) + len(j.Left().Schema()) + len(j.Right().Schema()), scopeLen: j.ScopeLen, parentLen: parentLen, + + rowBuffer: rowBuffer, }), nil } @@ -664,7 +694,7 @@ func (i *crossJoinIterator) Next(ctx *sql.Context) (sql.Row, error) { return nil, err } - row := make(sql.Row, i.rowSize) + row := i.rowBuffer.Get(i.rowSize) copy(row, i.primaryRow) copy(row[len(i.primaryRow):], rightRow) return i.removeParentRow(row), nil @@ -678,6 +708,9 @@ func (i *crossJoinIterator) removeParentRow(r sql.Row) sql.Row { } func (i *crossJoinIterator) Close(ctx *sql.Context) (err error) { + i.rowBuffer.Reset() + sql.RowBufPool.Put(i.rowBuffer) + if i.l != nil { err = i.l.Close(ctx) } @@ -734,6 +767,8 @@ type lateralJoinIterator struct { foundMatch bool b sql.NodeExecBuilder + + rowBuffer *sql.RowBuffer } func newLateralJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, row sql.Row) (sql.RowIter, error) { @@ -769,6 +804,8 @@ func newLateralJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNod rowSize: len(row) + len(j.Left().Schema()) + len(j.Right().Schema()), scopeLen: j.ScopeLen, b: b, + + rowBuffer: sql.RowBufPool.Get().(*sql.RowBuffer), }), nil } @@ -811,7 +848,7 @@ func (i *lateralJoinIterator) loadRight(ctx *sql.Context) error { } func (i *lateralJoinIterator) buildRow(lRow, rRow sql.Row) sql.Row { - row := make(sql.Row, i.rowSize) + row := i.rowBuffer.Get(i.rowSize) copy(row, lRow) copy(row[len(lRow):], rRow) return row @@ -874,6 +911,9 @@ func (i *lateralJoinIterator) Next(ctx *sql.Context) (sql.Row, error) { } func (i *lateralJoinIterator) Close(ctx *sql.Context) error { + i.rowBuffer.Reset() + sql.RowBufPool.Put(i.rowBuffer) + var lerr, rerr error if i.lIter != nil { lerr = i.lIter.Close(ctx) diff --git a/sql/rowexec/merge_join.go b/sql/rowexec/merge_join.go index 5a92bcd9f2..a9570903fb 100644 --- a/sql/rowexec/merge_join.go +++ b/sql/rowexec/merge_join.go @@ -46,8 +46,11 @@ func newMergeJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, return nil, err } - fullRow := make(sql.Row, len(row)+len(j.Left().Schema())+len(j.Right().Schema())) - fullRow[0] = row + //fullRow := make(sql.Row, len(row)+len(j.Left().Schema())+len(j.Right().Schema())) + + rowBuf := sql.RowBufPool.Get().(*sql.RowBuffer) + fullRow := rowBuf.Get(len(row) + len(j.Left().Schema()) + len(j.Right().Schema())) + //fullRow[0] = row if len(row) > 0 { copy(fullRow[0:], row[:]) } @@ -83,6 +86,7 @@ func newMergeJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, leftRowLen: len(j.Left().Schema()), rightRowLen: len(j.Right().Schema()), isReversed: j.IsReversed, + rowBuffer: rowBuf, } return iter, nil } @@ -128,6 +132,8 @@ type mergeJoinIter struct { leftRowLen int rightRowLen int parentLen int + + rowBuffer *sql.RowBuffer } var _ sql.RowIter = (*mergeJoinIter)(nil) @@ -574,6 +580,9 @@ func (i *mergeJoinIter) removeParentRow(r sql.Row) sql.Row { } func (i *mergeJoinIter) Close(ctx *sql.Context) (err error) { + i.rowBuffer.Reset() + sql.RowBufPool.Put(i.rowBuffer) + if i.left != nil { err = i.left.Close(ctx) } diff --git a/sql/rowexec/range_heap_iter.go b/sql/rowexec/range_heap_iter.go index 12182bbfcf..737c20917b 100644 --- a/sql/rowexec/range_heap_iter.go +++ b/sql/rowexec/range_heap_iter.go @@ -36,6 +36,8 @@ type rangeHeapJoinIter struct { childRowIter sql.RowIter pendingRow sql.Row activeRanges []sql.Row + + rowBuffer *sql.RowBuffer } func newRangeHeapJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinNode, row sql.Row) (sql.RowIter, error) { @@ -68,9 +70,11 @@ func newRangeHeapJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinN return nil, errors.New("right side of join must be a range heap") } + rowBuffer := sql.RowBufPool.Get().(*sql.RowBuffer) + parentLen := len(row) - primaryRow := make(sql.Row, parentLen+len(j.Left().Schema())) + primaryRow := rowBuffer.Get(parentLen + len(j.Left().Schema())) copy(primaryRow, row) return sql.NewSpanIter(span, &rangeHeapJoinIter{ @@ -88,6 +92,8 @@ func newRangeHeapJoinIter(ctx *sql.Context, b sql.NodeExecBuilder, j *plan.JoinN parentLen: parentLen, rangeHeapPlan: rhp, + + rowBuffer: rowBuffer, }), nil } @@ -202,13 +208,16 @@ func (iter *rangeHeapJoinIter) removeParentRow(r sql.Row) sql.Row { // buildRow builds the result set row using the rows from the primary and secondary tables func (iter *rangeHeapJoinIter) buildRow(primary, secondary sql.Row) sql.Row { - row := make(sql.Row, iter.rowSize) + row := iter.rowBuffer.Get(iter.rowSize) copy(row, primary) copy(row[len(primary):], secondary) return row } func (iter *rangeHeapJoinIter) Close(ctx *sql.Context) (err error) { + iter.rowBuffer.Reset() + sql.RowBufPool.Put(iter.rowBuffer) + if iter.primary != nil { if err = iter.primary.Close(ctx); err != nil { if iter.secondary != nil { diff --git a/sql/rowexec/rel.go b/sql/rowexec/rel.go index d5ea5107e0..2179d88a9b 100644 --- a/sql/rowexec/rel.go +++ b/sql/rowexec/rel.go @@ -97,7 +97,7 @@ func (b *BaseBuilder) buildValues(ctx *sql.Context, n *plan.Values, row sql.Row) // For the values node, the relevant values to evaluate are the tuple itself. We may need to project // DEFAULT values onto them, which ProjectRow handles correctly (could require multiple passes) var err error - vals, err = ProjectRow(ctx, et, vals) + vals, err = ProjectRow(ctx, et, vals, nil) if err != nil { return nil, err } @@ -315,6 +315,7 @@ func (b *BaseBuilder) buildProject(ctx *sql.Context, n *plan.Project, row sql.Ro canDefer: n.CanDefer, hasNestedIters: n.IncludesNestedIters, childIter: i, + rowBuffer: sql.RowBufPool.Get().(*sql.RowBuffer), }), nil } @@ -326,6 +327,7 @@ func (b *BaseBuilder) buildVirtualColumnTable(ctx *sql.Context, n *plan.VirtualC return sql.NewSpanIter(span, &ProjectIter{ projs: n.Projections, childIter: tableIter, + rowBuffer: sql.RowBufPool.Get().(*sql.RowBuffer), }), nil } diff --git a/sql/rowexec/rel_iters.go b/sql/rowexec/rel_iters.go index 0d911ebc96..12e3a562b6 100644 --- a/sql/rowexec/rel_iters.go +++ b/sql/rowexec/rel_iters.go @@ -132,6 +132,8 @@ type ProjectIter struct { hasNestedIters bool nestedState *nestedIterState childIter sql.RowIter + + rowBuffer *sql.RowBuffer } type nestedIterState struct { @@ -150,10 +152,12 @@ func (i *ProjectIter) Next(ctx *sql.Context) (sql.Row, error) { return nil, err } - return ProjectRow(ctx, i.projs, childRow) + return ProjectRow(ctx, i.projs, childRow, i.rowBuffer) } func (i *ProjectIter) Close(ctx *sql.Context) error { + i.rowBuffer.Reset() + sql.RowBufPool.Put(i.rowBuffer) return i.childIter.Close(ctx) } @@ -178,7 +182,7 @@ func (i *ProjectIter) ProjectRowWithNestedIters( // Other iterator values will be NULL after they are depleted. All non-iterator fields for the row are returned // identically for each row in the result set. if i.nestedState != nil { - row, err := ProjectRow(ctx, i.nestedState.projections, i.nestedState.sourceRow) + row, err := ProjectRow(ctx, i.nestedState.projections, i.nestedState.sourceRow, nil) if err != nil { return nil, err } @@ -304,8 +308,14 @@ func ProjectRow( ctx *sql.Context, projections []sql.Expression, row sql.Row, + rowBuffer *sql.RowBuffer, ) (sql.Row, error) { - var fields = make(sql.Row, len(projections)) + var fields sql.Row + if rowBuffer != nil { + fields = rowBuffer.Get(len(projections)) + } else { + fields = make(sql.Row, len(projections)) + } var secondPass []int for i, expr := range projections { // Default values that are expressions may reference other fields, thus they must evaluate after all other exprs. diff --git a/sql/rows.go b/sql/rows.go index bd89fd7cd4..a078eae91e 100644 --- a/sql/rows.go +++ b/sql/rows.go @@ -18,6 +18,7 @@ import ( "fmt" "io" "strings" + "sync" "github.com/dolthub/vitess/go/vt/proto/query" @@ -83,6 +84,52 @@ func FormatRow(row Row) string { return sb.String() } +const defaultRowBufCap = 128 + +type RowBuffer struct { + i int + buf Row +} + +func NewRowBuffer() *RowBuffer { + return &RowBuffer{ + buf: make(Row, 0, defaultRowBufCap), + } +} + +func (b *RowBuffer) Get(n int) (res Row) { + newI := b.i + n + if newI >= cap(b.buf) { + buf := make(Row, newI*2) // Golang is stupid, don't let it decide the new capacity + copy(buf, b.buf) + b.buf = buf + } + b.buf = b.buf[:newI] + for i := b.i; i < newI; i++ { + b.buf[i] = nil + } + res = b.buf[b.i:newI] + b.i = newI + return +} + +func (b *RowBuffer) Reset() { + for i := 0; i < b.i; i++ { + b.buf[i] = nil + } + b.i = 0 +} + +func (b *RowBuffer) Erase(i int) { + b.i -= i +} + +var RowBufPool = sync.Pool{ + New: func() any { + return NewRowBuffer() + }, +} + // RowIter is an iterator that produces rows. // TODO: most row iters need to be Disposable for CachedResult safety type RowIter interface { @@ -106,7 +153,7 @@ func RowIterToRows(ctx *Context, i RowIter) ([]Row, error) { return nil, err } - rows = append(rows, row) + rows = append(rows, row.Copy()) } return rows, i.Close(ctx) @@ -179,7 +226,11 @@ func rowFromRow2(sch Schema, r Row2) Row { // RowsToRowIter creates a RowIter that iterates over the given rows. func RowsToRowIter(rows ...Row) RowIter { - return &sliceRowIter{rows: rows} + allRows := make([]Row, len(rows)) + for i, row := range rows { + allRows[i] = row.Copy() + } + return &sliceRowIter{rows: allRows} } type sliceRowIter struct { @@ -191,10 +242,9 @@ func (i *sliceRowIter) Next(*Context) (Row, error) { if i.idx >= len(i.rows) { return nil, io.EOF } - r := i.rows[i.idx] i.idx++ - return r.Copy(), nil + return r, nil } func (i *sliceRowIter) Close(*Context) error {