func (f *Frame) Joined(frame *Frame) (*Frame, error) {
	fr := NewFrame(JoinResultIndexer{f.indexer})
	all, err := f.GetRange()
	if err != nil {
		return nil, err
	}
	for _, row := range all {
		projected := make(map[string]interface{})
		for col, val := range row {
			projected[col] = &JoinResult{Left: val}
		}
		_, err := fr.Put(projected)
		if err != nil {
			return nil, err
		}
	}
	all, err = frame.GetRange()
	if err != nil {
		return nil, err
	}
	for _, row := range all {
		projected, err := fr.Get(row)
		if err != nil {
			return nil, err
		}
		if projected == nil {
			projected = make(map[string]interface{})
		}
		for col, val := range row {
			joinResult, ok := projected[col]
			if ok {
				jd, ok := joinResult.(*JoinResult)
				if !ok {
					return nil, fmt.Errorf("Frame: column %q in %v is not a JoinResult", col, joinResult)
				}
				jd.Right = val
			} else {
				projected[col] = &JoinResult{Right: val}
			}
		}
		fr.Put(projected)
		if err != nil {
			return nil, err
		}
	}
	return fr, nil
}
Joined returns a new Frame object that contains the joined contents of the
two frames. The indices of the frames must be compatible. The resulting
Frame contains one row for each key in the union of keys for the left and
right frames. The Data contains a JoinResult for each column of data,
where Left is populated with the left side contents, and Right is populated
with the right side contents. Left and Right are nil if they don't exist in
the left and right sides.
package godata
import (
	"bytes"
	"fmt"
	"github.com/google/btree"
	"github.com/google/godata/group"
	"github.com/google/godata/row"
)
type Frame struct {
	bt *btree.BTree
	indexer row.Indexer
}
func NewFrame(indexer row.Indexer) *Frame {
	return &Frame{
		bt:      btree.New(2),
		indexer: indexer,
	}
}
func (f *Frame) Put(data row.Data) (row.Data, error) {
	index, err := f.indexer.Index(data)
	if err != nil {
		return nil, fmt.Errorf("AddRow: %v", err)
	}
	got := f.bt.ReplaceOrInsert(row.Row{
		Index: index,
		Data:  data,
	})
	if got == nil {
		return nil, nil
	}
	return got.(row.Row).Data, nil
}
func (f *Frame) Get(key row.Data) (row.Data, error) {
	index, err := f.indexer.Index(key)
	if err != nil {
		return nil, err
	}
	got := f.bt.Get(index)
	if got == nil {
		return nil, nil
	}
	return got.(row.Row).Data, nil
}
func (f *Frame) Pop(key row.Data) (row.Data, error) {
	index, err := f.indexer.Index(key)
	if err != nil {
		return nil, err
	}
	got := f.bt.Delete(index)
	if got == nil {
		return nil, nil
	}
	return got.(row.Row).Data, nil
}
type rangeOptions struct {
	greaterOrEqual row.Data
	lessThan       row.Data
}
type rangeArg func(*rangeOptions)
func rangeArgsToOptions(args []rangeArg) *rangeOptions {
	var opts rangeOptions
	for _, a := range args {
		a(&opts)
	}
	return &opts
}
func GreaterOrEqual(val row.Data) rangeArg {
	return func(opts *rangeOptions) {
		opts.greaterOrEqual = val
	}
}
func LessThan(val row.Data) rangeArg {
	return func(opts *rangeOptions) {
		opts.lessThan = val
	}
}
type RowAction func(row.Data) (interface{}, error)
type rowAction func(row.Row) (interface{}, error)
func (f *Frame) forRange(opts *rangeOptions, action rowAction) ([]interface{}, error) {
	var (
		returnError  error
		returnValues []interface{}
	)
	iterator := func(item btree.Item) bool {
		val, err := action(item.(row.Row))
		if err != nil {
			returnError = err
			return false
		}
		returnValues = append(returnValues, val)
		return true
	}
	if opts.lessThan == nil && opts.greaterOrEqual == nil {
		f.bt.Ascend(iterator)
	} else if opts.lessThan == nil {
		pivot, err := f.indexer.Index(opts.greaterOrEqual)
		if err != nil {
			return nil, err
		}
		f.bt.AscendGreaterOrEqual(pivot, iterator)
	} else if opts.greaterOrEqual == nil {
		pivot, err := f.indexer.Index(opts.lessThan)
		if err != nil {
			return nil, err
		}
		f.bt.AscendLessThan(pivot, iterator)
	} else {
		begin, err := f.indexer.Index(opts.greaterOrEqual)
		if err != nil {
			return nil, err
		}
		end, err := f.indexer.Index(opts.lessThan)
		if err != nil {
			return nil, err
		}
		f.bt.AscendRange(begin, end, iterator)
	}
	return returnValues, returnError
}
func (f *Frame) String() string {
	rows, err := f.GetRange()
	if err != nil {
		return fmt.Sprintf("Frame.String: %v", err)
	}
	var buf bytes.Buffer
	for i, r := range rows {
		buf.WriteString(fmt.Sprintf("%d: %v\n", i, r))
	}
	return buf.String()
}
func (f *Frame) GetRange(args ...rangeArg) ([]row.Data, error) {
	opts := rangeArgsToOptions(args)
	rows, err := f.forRange(opts, func(row row.Row) (interface{}, error) {
		return row.Data, nil
	})
	if err != nil {
		return nil, err
	}
	var castRows []row.Data
	for _, r := range rows {
		castRows = append(castRows, r.(row.Data))
	}
	return castRows, nil
}
func (f *Frame) PopRange(args ...rangeArg) ([]row.Data, error) {
	opts := rangeArgsToOptions(args)
	rows, err := f.forRange(opts, func(row row.Row) (interface{}, error) {
		return row, nil
	})
	if err != nil {
		return nil, err
	}
	var (
		indices []row.Index
		data    []row.Data
	)
	for _, r := range rows {
		indices = append(indices, r.(row.Row).Index)
		data = append(data, r.(row.Row).Data)
	}
	for _, i := range indices {
		f.bt.Delete(i)
	}
	return data, nil
}