Merge pull request #3069 from dolthub/andy/complex-index-queries

[no-release-notes] go/{store/prolly, libraries/doltcore/sqle}: Redefine `prolly.Range` to support all range queries
This commit is contained in:
AndyA
2022-03-29 09:03:46 -07:00
committed by GitHub
19 changed files with 1166 additions and 748 deletions

View File

@@ -62,19 +62,44 @@ func TestSingleScript(t *testing.T) {
var scripts = []enginetest.ScriptTest{
{
Name: "insert into sparse auto_increment table",
Name: "Two column index",
SetUpScript: []string{
"create table auto (pk int primary key auto_increment)",
"insert into auto values (10), (20), (30)",
"insert into auto values (NULL)",
"insert into auto values (40)",
"insert into auto values (0)",
`CREATE TABLE test (pk BIGINT PRIMARY KEY, v1 BIGINT, v2 BIGINT, INDEX (v1, v2));`,
`INSERT INTO test VALUES (0,0,48),(1,0,52),(2,2,4),(3,2,10),(4,3,35),(5,5,36),(6,5,60),(7,6,1),(8,6,51),
(9,6,60),(10,6,73),(11,9,44),(12,9,97),(13,13,44),(14,14,53),(15,14,57),(16,14,98),(17,16,19),(18,16,53),(19,16,95),
(20,18,31),(21,19,48),(22,19,75),(23,19,97),(24,24,60),(25,25,14),(26,25,31),(27,27,9),(28,27,24),(29,28,24),(30,28,83),
(31,31,14),(32,33,39),(33,34,22),(34,34,91),(35,35,89),(36,38,20),(37,38,66),(38,39,55),(39,39,86),(40,40,97),(41,42,0),
(42,42,82),(43,43,63),(44,44,48),(45,44,67),(46,45,22),(47,45,31),(48,45,63),(49,45,86),(50,46,46),(51,47,5),(52,48,22),
(53,49,0),(54,50,0),(55,50,14),(56,51,35),(57,54,38),(58,56,0),(59,56,60),(60,57,29),(61,57,49),(62,58,12),(63,58,32),
(64,59,29),(65,59,45),(66,59,54),(67,60,66),(68,61,3),(69,61,34),(70,63,19),(71,63,69),(72,65,80),(73,65,97),(74,67,95),
(75,68,11),(76,69,34),(77,72,52),(78,74,81),(79,76,39),(80,78,0),(81,78,90),(82,79,36),(83,80,61),(84,80,88),(85,81,4),
(86,82,16),(87,83,30),(88,83,74),(89,84,9),(90,84,45),(91,86,56),(92,86,88),(93,87,51),(94,89,3),(95,93,19),(96,93,21),
(97,93,96),(98,98,0),(99,98,51),(100,98,61);`,
},
Assertions: []enginetest.ScriptTestAssertion{
{
Query: "select * from auto order by 1",
Query: "SELECT * FROM test WHERE (((v1<20 AND v2<=46) OR (v1<>4 AND v2=26)) OR (v1>36 AND v2<>13));",
Expected: []sql.Row{
{10}, {20}, {30}, {31}, {40}, {41},
{58, 56, 0}, {61, 57, 49}, {72, 65, 80}, {85, 81, 4}, {3, 2, 10}, {49, 45, 86}, {5, 5, 36}, {50, 46, 46}, {62, 58, 12}, {92, 86, 88}, {47, 45, 31}, {54, 50, 0}, {55, 50, 14}, {87, 83, 30}, {91, 86, 56}, {66, 59, 54}, {76, 69, 34}, {79, 76, 39}, {46, 45, 22}, {57, 54, 38}, {68, 61, 3}, {93, 87, 51}, {4, 3, 35}, {7, 6, 1}, {45, 44, 67}, {52, 48, 22}, {2, 2, 4}, {53, 49, 0}, {69, 61, 34}, {73, 65, 97}, {90, 84, 45}, {82, 79, 36}, {11, 9, 44}, {20, 18, 31}, {41, 42, 0}, {43, 43, 63}, {65, 59, 45}, {100, 98, 61}, {95, 93, 19}, {13, 13, 44}, {56, 51, 35}, {59, 56, 60}, {67, 60, 66}, {77, 72, 52}, {89, 84, 9}, {63, 58, 32}, {83, 80, 61}, {39, 39, 86}, {17, 16, 19}, {38, 39, 55}, {40, 40, 97}, {74, 67, 95}, {78, 74, 81}, {81, 78, 90}, {88, 83, 74}, {37, 38, 66}, {48, 45, 63}, {51, 47, 5}, {64, 59, 29}, {80, 78, 0}, {86, 82, 16}, {96, 93, 21}, {98, 98, 0}, {75, 68, 11}, {84, 80, 88}, {99, 98, 51}, {44, 44, 48}, {60, 57, 29}, {70, 63, 19}, {71, 63, 69}, {36, 38, 20}, {42, 42, 82}, {94, 89, 3}, {97, 93, 96},
},
},
{
Query: "SELECT * FROM test WHERE (((v1<=52 AND v2<40) AND (v1<30) OR (v1<=75 AND v2 BETWEEN 54 AND 54)) OR (v1<>31 AND v2<>56));",
Expected: []sql.Row{
{19, 16, 95}, {58, 56, 0}, {61, 57, 49}, {72, 65, 80}, {85, 81, 4}, {3, 2, 10}, {49, 45, 86}, {5, 5, 36}, {9, 6, 60}, {50, 46, 46}, {62, 58, 12}, {92, 86, 88}, {15, 14, 57}, {47, 45, 31}, {54, 50, 0}, {55, 50, 14}, {87, 83, 30}, {16, 14, 98}, {66, 59, 54}, {76, 69, 34}, {79, 76, 39}, {21, 19, 48}, {46, 45, 22}, {57, 54, 38}, {68, 61, 3}, {93, 87, 51}, {4, 3, 35}, {7, 6, 1}, {45, 44, 67}, {52, 48, 22}, {2, 2, 4}, {12, 9, 97}, {30, 28, 83}, {53, 49, 0}, {69, 61, 34}, {73, 65, 97}, {90, 84, 45}, {82, 79, 36}, {0, 0, 48}, {10, 6, 73}, {11, 9, 44}, {20, 18, 31}, {41, 42, 0}, {43, 43, 63}, {65, 59, 45}, {100, 98, 61}, {95, 93, 19}, {1, 0, 52}, {13, 13, 44}, {56, 51, 35}, {59, 56, 60}, {67, 60, 66}, {77, 72, 52}, {89, 84, 9}, {24, 24, 60}, {33, 34, 22}, {35, 35, 89}, {63, 58, 32}, {83, 80, 61}, {39, 39, 86}, {8, 6, 51}, {14, 14, 53}, {17, 16, 19}, {23, 19, 97}, {26, 25, 31}, {29, 28, 24}, {38, 39, 55}, {40, 40, 97}, {74, 67, 95}, {78, 74, 81}, {81, 78, 90}, {88, 83, 74}, {28, 27, 24}, {37, 38, 66}, {48, 45, 63}, {51, 47, 5}, {64, 59, 29}, {80, 78, 0}, {86, 82, 16}, {96, 93, 21}, {98, 98, 0}, {25, 25, 14}, {27, 27, 9}, {32, 33, 39}, {75, 68, 11}, {84, 80, 88}, {99, 98, 51}, {6, 5, 60}, {22, 19, 75}, {44, 44, 48}, {60, 57, 29}, {70, 63, 19}, {71, 63, 69}, {18, 16, 53}, {34, 34, 91}, {36, 38, 20}, {42, 42, 82}, {94, 89, 3}, {97, 93, 96},
},
},
{
Query: "SELECT * FROM test WHERE ((v1>42 AND v2<=13) OR (v1=7));",
Expected: []sql.Row{
{58, 56, 0}, {85, 81, 4}, {62, 58, 12}, {54, 50, 0}, {68, 61, 3}, {53, 49, 0}, {89, 84, 9}, {51, 47, 5}, {80, 78, 0}, {98, 98, 0}, {75, 68, 11}, {94, 89, 3},
},
},
{
Query: "SELECT * FROM test WHERE (((((v1<71 AND v2<7) OR (v1<=21 AND v2<=48)) OR (v1=44 AND v2 BETWEEN 21 AND 83)) OR (v1<=72 AND v2<>27)) OR (v1=35 AND v2 BETWEEN 78 AND 89));",
Expected: []sql.Row{
{19, 16, 95}, {58, 56, 0}, {61, 57, 49}, {72, 65, 80}, {3, 2, 10}, {49, 45, 86}, {5, 5, 36}, {9, 6, 60}, {50, 46, 46}, {62, 58, 12}, {15, 14, 57}, {47, 45, 31}, {54, 50, 0}, {55, 50, 14}, {16, 14, 98}, {66, 59, 54}, {76, 69, 34}, {21, 19, 48}, {46, 45, 22}, {57, 54, 38}, {68, 61, 3}, {4, 3, 35}, {7, 6, 1}, {45, 44, 67}, {52, 48, 22}, {2, 2, 4}, {12, 9, 97}, {30, 28, 83}, {53, 49, 0}, {69, 61, 34}, {73, 65, 97}, {0, 0, 48}, {10, 6, 73}, {11, 9, 44}, {20, 18, 31}, {41, 42, 0}, {43, 43, 63}, {65, 59, 45}, {1, 0, 52}, {13, 13, 44}, {56, 51, 35}, {59, 56, 60}, {67, 60, 66}, {77, 72, 52}, {24, 24, 60}, {33, 34, 22}, {35, 35, 89}, {63, 58, 32}, {39, 39, 86}, {8, 6, 51}, {14, 14, 53}, {17, 16, 19}, {23, 19, 97}, {26, 25, 31}, {29, 28, 24}, {38, 39, 55}, {40, 40, 97}, {74, 67, 95}, {28, 27, 24}, {37, 38, 66}, {48, 45, 63}, {51, 47, 5}, {64, 59, 29}, {25, 25, 14}, {27, 27, 9}, {32, 33, 39}, {75, 68, 11}, {6, 5, 60}, {22, 19, 75}, {31, 31, 14}, {44, 44, 48}, {60, 57, 29}, {70, 63, 19}, {71, 63, 69}, {18, 16, 53}, {34, 34, 91}, {36, 38, 20}, {42, 42, 82},
},
},
},
@@ -86,8 +111,8 @@ func TestSingleScript(t *testing.T) {
myDb := harness.NewDatabase("mydb")
databases := []sql.Database{myDb}
engine := enginetest.NewEngineWithDbs(t, harness, databases)
engine.Analyzer.Debug = true
engine.Analyzer.Verbose = true
//engine.Analyzer.Debug = true
//engine.Analyzer.Verbose = true
enginetest.TestScriptWithEngine(t, engine, harness, test)
}
}

View File

@@ -183,9 +183,14 @@ func (di doltIndex) newProllyLookup(ctx *sql.Context, ranges ...sql.Range) (sql.
}
}
// the sql engine provides ranges that are logically disjoint in value space.
// however, these ranges may overlap physically within the index. Here we merge
// physically overlapping ranges to avoid returning duplicate tuples/rows.
merged := prolly.MergeOverlappingRanges(prs...)
return &doltIndexLookup{
idx: di,
prollyRanges: prs,
prollyRanges: merged,
sqlRanges: sqlRanges,
}, nil
}
@@ -420,108 +425,78 @@ func pruneEmptyRanges(sqlRanges []sql.Range) (pruned []sql.Range, err error) {
return pruned, nil
}
func prollyRangeFromSqlRange(sqlRange sql.Range, tb *val.TupleBuilder) (rng prolly.Range, err error) {
var lower, upper []sql.RangeCut
for _, expr := range sqlRange {
lower = append(lower, expr.LowerBound)
upper = append(upper, expr.UpperBound)
func prollyRangeFromSqlRange(rng sql.Range, tb *val.TupleBuilder) (prolly.Range, error) {
prollyRange := prolly.Range{
Start: make([]prolly.RangeCut, len(rng)),
Stop: make([]prolly.RangeCut, len(rng)),
Desc: tb.Desc,
}
start := prolly.RangeCut{Inclusive: true}
startRow := sql.Row{}
for _, sc := range lower {
if !sql.RangeCutIsBinding(sc) {
start = prolly.RangeCut{Unbound: true, Inclusive: false}
break
for i, expr := range rng {
if !sql.RangeCutIsBinding(expr.LowerBound) {
continue
}
start.Inclusive = start.Inclusive && sc.TypeAsLowerBound() == sql.Closed
startRow = append(startRow, sql.GetRangeCutKey(sc))
}
if !start.Unbound {
startRow, err = normalizeRangeKey(sqlRange, startRow)
v, err := getRangeCutValue(expr.LowerBound, rng[i].Typ)
if err != nil {
return prolly.Range{}, err
}
start.Key, err = tupleFromKeys(startRow, tb)
if err != nil {
return prolly.Range{}, err
}
}
stop := prolly.RangeCut{Inclusive: true}
stopRow := sql.Row{}
for _, sc := range upper {
if !sql.RangeCutIsBinding(sc) {
stop = prolly.RangeCut{Unbound: true, Inclusive: false}
break
}
stop.Inclusive = stop.Inclusive && sc.TypeAsUpperBound() == sql.Closed
stopRow = append(stopRow, sql.GetRangeCutKey(sc))
}
if !stop.Unbound {
stopRow, err = normalizeRangeKey(sqlRange, stopRow)
if err != nil {
return prolly.Range{}, err
}
stop.Key, err = tupleFromKeys(stopRow, tb)
if err != nil {
return prolly.Range{}, err
}
}
rngDesc := tupleDescriptorForRange(tb.Desc, sqlRange, startRow, stopRow)
return prolly.Range{
Start: start,
Stop: stop,
KeyDesc: rngDesc,
}, nil
}
func tupleFromKeys(keys sql.Row, tb *val.TupleBuilder) (val.Tuple, error) {
var err error
for i, v := range keys {
if err = PutField(tb, i, v); err != nil {
return nil, err
return prolly.Range{}, err
}
}
// ranges can be defined using null values even if the index is non-null
return tb.BuildPermissive(sharePool), nil
}
// BuildPermissive() allows nulls in non-null fields
tup := tb.BuildPermissive(sharePool)
// normalizeRangeKey converts a range's key into a canonical value.
func normalizeRangeKey(rng sql.Range, key sql.Row) (sql.Row, error) {
var err error
for i := range key {
key[i], err = rng[i].Typ.Convert(key[i])
for i, expr := range rng {
if !sql.RangeCutIsBinding(expr.LowerBound) {
continue
}
bound := expr.LowerBound.TypeAsLowerBound()
_, null := expr.LowerBound.(sql.NullBound)
prollyRange.Start[i] = prolly.RangeCut{
Value: tup.GetField(i),
Inclusive: bound == sql.Closed,
Null: null,
}
}
for i, expr := range rng {
if !sql.RangeCutIsBinding(expr.UpperBound) {
continue
}
v, err := getRangeCutValue(expr.UpperBound, rng[i].Typ)
if err != nil {
return nil, err
return prolly.Range{}, err
}
if err = PutField(tb, i, v); err != nil {
return prolly.Range{}, err
}
}
return key, nil
tup = tb.BuildPermissive(sharePool)
for i, expr := range rng {
if !sql.RangeCutIsBinding(expr.UpperBound) {
continue
}
bound := expr.UpperBound.TypeAsUpperBound()
_, null := expr.UpperBound.(sql.NullBound)
prollyRange.Stop[i] = prolly.RangeCut{
Value: tup.GetField(i),
Inclusive: bound == sql.Closed,
Null: null,
}
}
return prollyRange, nil
}
// tupleDescriptorForRange constructs a tuple descriptor suitable for range queries.
// Range queries can be made over a prefix subset of the index's columns, so we need
// a tuple descriptor that is aware of that subset.
// We also need to account for range keys containing nulls and disable tuple access
// methods that assume non-null tuples.
func tupleDescriptorForRange(desc val.TupleDesc, rng sql.Range, start, stop sql.Row) val.TupleDesc {
rngDesc := val.TupleDescriptorPrefix(desc, len(rng))
for i := range start {
if start[i] == nil {
return rngDesc.WithoutFixedAccess()
}
}
for i := range stop {
if stop[i] == nil {
return rngDesc.WithoutFixedAccess()
}
}
return rngDesc
func getRangeCutValue(cut sql.RangeCut, typ sql.Type) (interface{}, error) {
return typ.Convert(sql.GetRangeCutKey(cut))
}

View File

@@ -203,11 +203,7 @@ func TableToRowIter(ctx *sql.Context, table *WritableDoltTable, columns []string
if types.IsFormat_DOLT_1(data.Format()) {
m := durable.ProllyMapFromIndex(data)
kd, _ := m.Descriptors()
p.rowRange = prolly.Range{
Start: prolly.RangeCut{Unbound: true},
Stop: prolly.RangeCut{Unbound: true},
KeyDesc: kd,
}
p.rowRange = prolly.Range{Start: nil, Stop: nil, Desc: kd}
}
return newRowIterator(ctx, t, columns, p)

View File

@@ -739,11 +739,7 @@ func partitionsFromProllyRows(rows durable.Index) []doltTablePartition {
// naively divide map by top-level keys
keys := prolly.PartitionKeysFromMap(pm)
first := prolly.Range{
Start: prolly.RangeCut{Unbound: true},
Stop: prolly.RangeCut{Key: keys[0], Inclusive: true},
KeyDesc: keyDesc,
}
first := prolly.LesserOrEqualRange(keys[0], keyDesc)
parts := make([]doltTablePartition, len(keys))
parts[0] = doltTablePartition{rowRange: first, rowData: rows}
@@ -751,11 +747,7 @@ func partitionsFromProllyRows(rows durable.Index) []doltTablePartition {
if i == 0 {
continue
}
rng := prolly.Range{
Start: prolly.RangeCut{Key: keys[i-1], Inclusive: false},
Stop: prolly.RangeCut{Key: keys[i], Inclusive: true},
KeyDesc: keyDesc,
}
rng := prolly.OpenStartRange(keys[i-1], keys[i], keyDesc)
parts[i] = doltTablePartition{rowRange: rng, rowData: rows}
}

View File

@@ -25,7 +25,7 @@ import (
"github.com/dolthub/dolt/go/store/val"
)
var expected = hash.Hash{
var goldenHash = hash.Hash{
0x0, 0x26, 0x55, 0xec, 0x3,
0x30, 0x52, 0xed, 0xdc, 0x9a,
0xdd, 0xe, 0x76, 0x4f, 0x3f,
@@ -36,7 +36,7 @@ func TestContentAddress(t *testing.T) {
keys, values := ascendingIntTuples(t, 12345)
m := makeTree(t, keys, values)
require.NotNil(t, m)
require.Equal(t, expected, m.hashOf())
require.Equal(t, goldenHash, m.hashOf())
assert.Equal(t, 12345, m.treeCount())
}

View File

@@ -17,8 +17,6 @@ package prolly
import (
"context"
"fmt"
"io"
"sort"
"github.com/dolthub/dolt/go/store/hash"
"github.com/dolthub/dolt/go/store/types"
@@ -166,11 +164,7 @@ func (m Map) Last(ctx context.Context) (key, value val.Tuple, err error) {
// IterAll returns a MutableMapRangeIter that iterates over the entire Map.
func (m Map) IterAll(ctx context.Context) (MapRangeIter, error) {
rng := Range{
Start: RangeCut{Unbound: true},
Stop: RangeCut{Unbound: true},
KeyDesc: m.keyDesc,
}
rng := Range{Start: nil, Stop: nil, Desc: m.keyDesc}
return m.IterRange(ctx, rng)
}
@@ -186,21 +180,21 @@ func (m Map) iterFromRange(ctx context.Context, rng Range) (*prollyRangeIter, er
stop *nodeCursor
)
startSearch := m.rangeStartSearchFn(rng)
if rng.Start.Unbound {
startSearch := rangeStartSearchFn(rng)
if rng.Start == nil {
start, err = newCursorAtStart(ctx, m.ns, m.root)
} else {
start, err = newCursorAtTuple(ctx, m.ns, m.root, rng.Start.Key, startSearch)
start, err = newCursorFromSearchFn(ctx, m.ns, m.root, startSearch)
}
if err != nil {
return nil, err
}
stopSearch := m.rangeStopSearchFn(rng)
if rng.Stop.Unbound {
stopSearch := rangeStopSearchFn(rng)
if rng.Stop == nil {
stop, err = newCursorPastEnd(ctx, m.ns, m.root)
} else {
stop, err = newCursorAtTuple(ctx, m.ns, m.root, rng.Stop.Key, stopSearch)
stop, err = newCursorFromSearchFn(ctx, m.ns, m.root, stopSearch)
}
if err != nil {
return nil, err
@@ -216,42 +210,6 @@ func (m Map) iterFromRange(ctx context.Context, rng Range) (*prollyRangeIter, er
}, nil
}
func (m Map) rangeStartSearchFn(rng Range) searchFn {
// todo(andy): inline sort.Search()
return func(query nodeItem, nd Node) int {
return sort.Search(int(nd.count), func(i int) bool {
q := val.Tuple(query)
t := val.Tuple(nd.getKey(i))
// compare using the range's tuple descriptor.
cmp := rng.KeyDesc.Compare(q, t)
if rng.Start.Inclusive {
return cmp <= 0
} else {
return cmp < 0
}
})
}
}
func (m Map) rangeStopSearchFn(rng Range) searchFn {
// todo(andy): inline sort.Search()
return func(query nodeItem, nd Node) int {
return sort.Search(int(nd.count), func(i int) bool {
q := val.Tuple(query)
t := val.Tuple(nd.getKey(i))
// compare using the range's tuple descriptor.
cmp := rng.KeyDesc.Compare(q, t)
if rng.Stop.Inclusive {
return cmp < 0
} else {
return cmp <= 0
}
})
}
}
// searchNode returns the smallest index where nd[i] >= query
// Adapted from search.Sort to inline comparison.
func (m Map) searchNode(query nodeItem, nd Node) int {
@@ -274,7 +232,7 @@ func (m Map) searchNode(query nodeItem, nd Node) int {
return i
}
var _ searchFn = Map{}.searchNode
var _ itemSearchFn = Map{}.searchNode
// compareItems is a compareFn.
func (m Map) compareItems(left, right nodeItem) int {
@@ -285,56 +243,3 @@ func (m Map) compareItems(left, right nodeItem) int {
func (m Map) compareKeys(left, right val.Tuple) int {
return int(m.keyDesc.Compare(left, right))
}
type prollyRangeIter struct {
// current tuple location
curr *nodeCursor
// non-inclusive range stop
stop *nodeCursor
}
var _ rangeIter = &prollyRangeIter{}
var _ MapRangeIter = &prollyRangeIter{}
func (it *prollyRangeIter) Next(ctx context.Context) (key, value val.Tuple, err error) {
if it.curr == nil {
return nil, nil, io.EOF
}
key = it.curr.nd.keys.GetSlice(it.curr.idx)
value = it.curr.nd.values.GetSlice(it.curr.idx)
_, err = it.curr.advance(ctx)
if err != nil {
return nil, nil, err
}
if it.curr.compare(it.stop) >= 0 {
// past the end of the range
it.curr = nil
}
return
}
func (it *prollyRangeIter) current() (key, value val.Tuple) {
// |it.curr| is set to nil when its range is exhausted
if it.curr != nil && it.curr.valid() {
key = it.curr.nd.keys.GetSlice(it.curr.idx)
value = it.curr.nd.values.GetSlice(it.curr.idx)
}
return
}
func (it *prollyRangeIter) iterate(ctx context.Context) (err error) {
_, err = it.curr.advance(ctx)
if err != nil {
return err
}
if it.curr.compare(it.stop) >= 0 {
// past the end of the range
it.curr = nil
}
return
}

View File

@@ -76,11 +76,7 @@ func (mm memoryMap) Get(_ context.Context, key val.Tuple, cb KeyValueFn) error {
// IterAll returns a MapIterator that iterates over the entire Map.
func (mm memoryMap) IterAll(ctx context.Context) (MapRangeIter, error) {
rng := Range{
Start: RangeCut{Unbound: true},
Stop: RangeCut{Unbound: true},
KeyDesc: mm.keyDesc,
}
rng := Range{Start: nil, Stop: nil, Desc: mm.keyDesc}
return mm.IterRange(ctx, rng)
}
@@ -92,25 +88,24 @@ func (mm memoryMap) IterRange(ctx context.Context, rng Range) (MapRangeIter, err
func (mm memoryMap) iterFromRange(rng Range) *memRangeIter {
var iter *skip.ListIter
if rng.Start.Unbound {
if rng.Start == nil {
iter = mm.list.IterAtStart()
} else {
vc := valueCmpForRange(rng)
iter = mm.list.GetIterAtWithFn(rng.Start.Key, vc)
iter = mm.list.GetIterFromSearchFn(skipSearchFromRange(rng))
}
// enforce range start
var key val.Tuple
for {
key, _ = iter.Current()
if key == nil || rng.insideStart(key) {
if key == nil || rng.AboveStart(key) {
break // |i| inside |rng|
}
iter.Advance()
}
// enforce range end
if key == nil || !rng.insideStop(key) {
if key == nil || !rng.BelowStop(key) {
iter = nil
}
@@ -120,21 +115,20 @@ func (mm memoryMap) iterFromRange(rng Range) *memRangeIter {
}
}
func valueCmpForRange(rng Range) skip.ValueCmp {
return func(left, right []byte) int {
l, r := val.Tuple(left), val.Tuple(right)
return rng.KeyDesc.Compare(l, r)
func skipSearchFromRange(rng Range) skip.SearchFn {
return func(nodeKey []byte) bool {
if nodeKey == nil {
return false
}
// advance through list until we're inside |rng|
return !rng.AboveStart(nodeKey)
}
}
func (mm memoryMap) mutations() mutationIter {
return &memRangeIter{
iter: mm.list.IterAtStart(),
rng: Range{
Start: RangeCut{Unbound: true},
Stop: RangeCut{Unbound: true},
KeyDesc: mm.keyDesc,
},
rng: Range{Start: nil, Stop: nil, Desc: mm.keyDesc},
}
}
@@ -163,7 +157,7 @@ func (it *memRangeIter) iterate(context.Context) (err error) {
it.iter.Advance()
k, _ := it.current()
if k == nil || !it.rng.insideStop(k) {
if k == nil || !it.rng.BelowStop(k) {
it.iter = nil // range exhausted
}

View File

@@ -79,11 +79,7 @@ func (mut MutableMap) Has(ctx context.Context, key val.Tuple) (ok bool, err erro
// IterAll returns a MutableMapRangeIter that iterates over the entire MutableMap.
func (mut MutableMap) IterAll(ctx context.Context) (MapRangeIter, error) {
rng := Range{
Start: RangeCut{Unbound: true},
Stop: RangeCut{Unbound: true},
KeyDesc: mut.prolly.keyDesc,
}
rng := Range{Start: nil, Stop: nil, Desc: mut.prolly.keyDesc}
return mut.IterRange(ctx, rng)
}

View File

@@ -46,7 +46,9 @@ type nodeCursor struct {
type compareFn func(left, right nodeItem) int
type searchFn func(item nodeItem, nd Node) (idx int)
type searchFn func(nd Node) (idx int)
type itemSearchFn func(item nodeItem, nd Node) (idx int)
func newCursorAtStart(ctx context.Context, nrw NodeStore, nd Node) (cur *nodeCursor, err error) {
cur = &nodeCursor{nd: nd, nrw: nrw}
@@ -97,11 +99,34 @@ func newCursorPastEnd(ctx context.Context, nrw NodeStore, nd Node) (cur *nodeCur
return
}
func newCursorAtTuple(ctx context.Context, nrw NodeStore, nd Node, tup val.Tuple, search searchFn) (cur *nodeCursor, err error) {
func newCursorFromSearchFn(ctx context.Context, nrw NodeStore, nd Node, search searchFn) (cur *nodeCursor, err error) {
cur = &nodeCursor{nd: nd, nrw: nrw}
cur.idx = search(cur.nd)
for !cur.isLeaf() {
// stay in bounds for internal nodes
cur.keepInBounds()
nd, err = fetchChild(ctx, nrw, cur.currentRef())
if err != nil {
return cur, err
}
parent := cur
cur = &nodeCursor{nd: nd, parent: parent, nrw: nrw}
cur.idx = search(cur.nd)
}
return
}
func newCursorAtTuple(ctx context.Context, nrw NodeStore, nd Node, tup val.Tuple, search itemSearchFn) (cur *nodeCursor, err error) {
return newCursorAtItem(ctx, nrw, nd, nodeItem(tup), search)
}
func newCursorAtItem(ctx context.Context, nrw NodeStore, nd Node, item nodeItem, search searchFn) (cur *nodeCursor, err error) {
func newCursorAtItem(ctx context.Context, nrw NodeStore, nd Node, item nodeItem, search itemSearchFn) (cur *nodeCursor, err error) {
cur = &nodeCursor{nd: nd, nrw: nrw}
cur.idx = search(item, cur.nd)
@@ -124,7 +149,7 @@ func newCursorAtItem(ctx context.Context, nrw NodeStore, nd Node, item nodeItem,
return
}
func newLeafCursorAtItem(ctx context.Context, nrw NodeStore, nd Node, item nodeItem, search searchFn) (cur nodeCursor, err error) {
func newLeafCursorAtItem(ctx context.Context, nrw NodeStore, nd Node, item nodeItem, search itemSearchFn) (cur nodeCursor, err error) {
cur = nodeCursor{nd: nd, parent: nil, nrw: nrw}
cur.idx = search(item, cur.nd)

View File

@@ -15,268 +15,373 @@
package prolly
import (
"context"
"io"
"fmt"
"sort"
"strings"
"github.com/dolthub/dolt/go/store/val"
)
// RangeCut bounds a Range.
type RangeCut struct {
Key val.Tuple
Inclusive bool
Unbound bool
// MergeOverlappingRanges merges overlapping ranges.
func MergeOverlappingRanges(ranges ...Range) (merged []Range) {
if len(ranges) <= 1 {
return ranges
}
ranges = SortRanges(ranges...)
merged = make([]Range, 0, len(ranges))
acc := ranges[0]
for _, rng := range ranges[1:] {
if acc.overlaps(rng) {
acc = acc.merge(rng)
} else {
merged = append(merged, acc)
acc = rng
}
}
merged = append(merged, acc)
return
}
// Range is a range of Tuples.
// SortRanges sorts ranges by start bound.
func SortRanges(ranges ...Range) []Range {
sort.Slice(ranges, func(i, j int) bool {
return ranges[i].less(ranges[j])
})
return ranges
}
// Range defines a contiguous range of Tuples starting from the
// lexicographically least Tuple that satisfies all RangeCut
// predicates, and ending at the greatest Tuple that satisfies
// all predicates. Tuples inside the Range need not satisfy
// all predicates, as long as they are in bounds.
type Range struct {
Start, Stop RangeCut
KeyDesc val.TupleDesc
Start, Stop []RangeCut
Desc val.TupleDesc
}
type MapRangeIter interface {
Next(ctx context.Context) (key, value val.Tuple, err error)
}
func (r Range) insideStart(key val.Tuple) bool {
if r.Start.Unbound {
// AboveStart returns true if |t| is a member of |r|.
func (r Range) AboveStart(t val.Tuple) bool {
if len(r.Start) == 0 {
return true
}
cmp := r.KeyDesc.Compare(key, r.Start.Key)
if cmp == 0 {
return r.Start.Inclusive
}
return cmp > 0
}
func (r Range) insideStop(key val.Tuple) bool {
if r.Stop.Unbound {
cut := r.Start[0]
if cut.nonBinding() {
return true
}
cmp := r.KeyDesc.Compare(key, r.Stop.Key)
if cut.Null {
// null values are returned iff |cut.Null|
return t.GetField(0) == nil
}
cmp := r.Desc.CompareField(cut.Value, 0, t)
return cmp < 0 || (cut.Inclusive && cmp == 0)
}
// BelowStop returns true if |t| is a member of |r|.
func (r Range) BelowStop(t val.Tuple) bool {
if len(r.Stop) == 0 {
return true
}
cut := r.Stop[0]
if cut.nonBinding() {
return true
}
if cut.Null {
// order nulls last
return true
}
cmp := r.Desc.CompareField(cut.Value, 0, t)
return cmp > 0 || (cut.Inclusive && cmp == 0)
}
func (r Range) less(other Range) bool {
assertTrue(len(r.Start) == len(other.Start))
if len(r.Start) == 0 {
return false
}
left, right := r.Start[0], other.Start[0]
if left.nonBinding() || right.nonBinding() {
// order unbound ranges first
return left.nonBinding() && right.binding()
}
compare := r.Desc.Comparator()
typ := r.Desc.Types[0]
return left.lesserValue(right, typ, compare)
}
func (r Range) overlaps(other Range) bool {
compare := r.Desc.Comparator()
typ := r.Desc.Types[0]
if r.Stop[0].binding() && other.Start[0].binding() {
if r.Stop[0].lesserValue(other.Start[0], typ, compare) {
return false
}
}
if other.Stop[0].binding() && r.Start[0].binding() {
if other.Stop[0].lesserValue(r.Start[0], typ, compare) {
return false
}
}
return true
}
func (r Range) merge(other Range) Range {
assertTrue(r.Desc.Equals(other.Desc))
assertTrue(len(r.Start) == len(other.Start))
assertTrue(len(r.Stop) == len(other.Stop))
types := r.Desc.Types
compare := r.Desc.Comparator()
// take the min of each RangeCut pair
lower := make([]RangeCut, len(r.Start))
for i := range lower {
left, right := r.Start[i], other.Start[i]
if left.nonBinding() || right.nonBinding() {
lower[i] = RangeCut{Value: nil}
continue
}
lower[i] = left
if right.lesserValue(left, types[i], compare) {
lower[i] = right
}
}
// take the max of each RangeCut pair
upper := make([]RangeCut, len(r.Stop))
for i := range upper {
left, right := r.Stop[i], other.Stop[i]
if left.nonBinding() || right.nonBinding() {
upper[i] = RangeCut{Value: nil}
continue
}
upper[i] = right
if right.lesserValue(left, types[i], compare) {
upper[i] = left
}
}
return Range{
Start: lower,
Stop: upper,
Desc: other.Desc,
}
}
func (r Range) format() string {
return formatRange(r)
}
// RangeCut bounds one dimension of a Range.
type RangeCut struct {
Value []byte
Inclusive bool
Null bool
}
func (c RangeCut) nonBinding() bool {
return c.Value == nil && c.Null == false
}
func (c RangeCut) binding() bool {
return c.Value != nil
}
func (c RangeCut) lesserValue(other RangeCut, typ val.Type, tc val.TupleComparator) bool {
if c.Null || other.Null {
// order nulls last
return !c.Null && other.Null
}
cmp := tc.CompareValues(c.Value, other.Value, typ)
if cmp == 0 {
return r.Stop.Inclusive
return c.Inclusive && !other.Inclusive
}
return cmp < 0
}
func NewMutableMapRangeIter(memory, prolly rangeIter, rng Range) MapRangeIter {
if memory == nil {
memory = emptyIter{}
}
if prolly == nil {
prolly = emptyIter{}
}
func rangeStartSearchFn(rng Range) searchFn {
return binarySearchRangeStart(rng)
}
return MutableMapRangeIter{
memory: memory,
prolly: prolly,
rng: rng,
func rangeStopSearchFn(rng Range) searchFn {
return binarySearchRangeStop(rng)
}
func binarySearchRangeStart(rng Range) searchFn {
return func(nd Node) int {
// todo(andy): inline sort.Search()
return sort.Search(int(nd.count), func(i int) (in bool) {
// if |tup| ∈ |rng|, set |in| to true
tup := val.Tuple(nd.getKey(i))
in = rng.AboveStart(tup)
return
})
}
}
// MutableMapRangeIter iterates over a Range of Tuples.
type MutableMapRangeIter struct {
memory rangeIter
prolly rangeIter
rng Range
}
type rangeIter interface {
iterate(ctx context.Context) error
current() (key, value val.Tuple)
}
// Next returns the next pair of Tuples in the Range, or io.EOF if the iter is done.
func (it MutableMapRangeIter) Next(ctx context.Context) (key, value val.Tuple, err error) {
for {
mk, mv := it.memory.current()
pk, pv := it.prolly.current()
if mk == nil && pk == nil {
// range is exhausted
return nil, nil, io.EOF
}
cmp := it.compareKeys(pk, mk)
switch {
case cmp < 0:
key, value = pk, pv
if err = it.prolly.iterate(ctx); err != nil {
return nil, nil, err
}
case cmp > 0:
key, value = mk, mv
if err = it.memory.iterate(ctx); err != nil {
return nil, nil, err
}
case cmp == 0:
// |it.memory| wins ties
key, value = mk, mv
if err = it.memory.iterate(ctx); err != nil {
return nil, nil, err
}
if err = it.prolly.iterate(ctx); err != nil {
return nil, nil, err
}
}
if key != nil && value == nil {
continue // pending delete
}
return key, value, nil
func binarySearchRangeStop(rng Range) searchFn {
return func(nd Node) (idx int) {
// todo(andy): inline sort.Search()
return sort.Search(int(nd.count), func(i int) (out bool) {
// if |tup| ∈ |rng|, set |out| to false
tup := val.Tuple(nd.getKey(i))
out = !rng.BelowStop(tup)
return
})
}
}
func (it MutableMapRangeIter) currentKeys() (memKey, proKey val.Tuple) {
if it.memory != nil {
memKey, _ = it.memory.current()
}
if it.prolly != nil {
proKey, _ = it.prolly.current()
}
return
}
func (it MutableMapRangeIter) compareKeys(memKey, proKey val.Tuple) int {
if memKey == nil {
return 1
}
if proKey == nil {
return -1
}
return it.rng.KeyDesc.Compare(memKey, proKey)
}
type emptyIter struct{}
var _ rangeIter = emptyIter{}
func (e emptyIter) iterate(ctx context.Context) (err error) {
return
}
func (e emptyIter) current() (key, value val.Tuple) {
return
}
// GreaterRange defines a Range of Tuples greater than |lo|.
// GreaterRange defines a Range of Tuples greater than |start|.
func GreaterRange(start val.Tuple, desc val.TupleDesc) Range {
return Range{
Start: RangeCut{
Key: start,
Inclusive: false,
},
Stop: RangeCut{
Unbound: true,
},
KeyDesc: desc,
Start: exclusiveBound(start, desc),
Desc: desc,
}
}
// GreaterOrEqualRange defines a Range of Tuples greater than or equal to |lo|.
// GreaterOrEqualRange defines a Range of Tuples greater than or equal to |start|.
func GreaterOrEqualRange(start val.Tuple, desc val.TupleDesc) Range {
return Range{
Start: RangeCut{
Key: start,
Inclusive: true,
},
Stop: RangeCut{
Unbound: true,
},
KeyDesc: desc,
Start: inclusiveBound(start, desc),
Desc: desc,
}
}
// LesserRange defines a Range of Tuples less than |last|.
// LesserRange defines a Range of Tuples less than |stop|.
func LesserRange(stop val.Tuple, desc val.TupleDesc) Range {
return Range{
Start: RangeCut{
Unbound: true,
},
Stop: RangeCut{
Key: stop,
Inclusive: false,
},
KeyDesc: desc,
Stop: exclusiveBound(stop, desc),
Desc: desc,
}
}
// LesserOrEqualRange defines a Range of Tuples less than or equal to |last|.
// LesserOrEqualRange defines a Range of Tuples less than or equal to |stop|.
func LesserOrEqualRange(stop val.Tuple, desc val.TupleDesc) Range {
return Range{
Start: RangeCut{
Unbound: true,
},
Stop: RangeCut{
Key: stop,
Inclusive: true,
},
KeyDesc: desc,
Stop: inclusiveBound(stop, desc),
Desc: desc,
}
}
// OpenRange defines a non-inclusive Range of Tuples from |lo| to |last|.
// OpenRange defines a non-inclusive Range of Tuples from |start| to |stop|.
func OpenRange(start, stop val.Tuple, desc val.TupleDesc) Range {
return Range{
Start: RangeCut{
Key: start,
Inclusive: false,
},
Stop: RangeCut{
Key: stop,
Inclusive: false,
},
KeyDesc: desc,
Start: exclusiveBound(start, desc),
Stop: exclusiveBound(stop, desc),
Desc: desc,
}
}
// OpenStartRange defines a half-open Range of Tuples from |lo| to |last|.
// OpenStartRange defines a half-open Range of Tuples from |start| to |stop|.
func OpenStartRange(start, stop val.Tuple, desc val.TupleDesc) Range {
return Range{
Start: RangeCut{
Key: start,
Inclusive: false,
},
Stop: RangeCut{
Key: stop,
Inclusive: true,
},
KeyDesc: desc,
Start: exclusiveBound(start, desc),
Stop: inclusiveBound(stop, desc),
Desc: desc,
}
}
// OpenStopRange defines a half-open Range of Tuples from |lo| to |last|.
// OpenStopRange defines a half-open Range of Tuples from |start| to |stop|.
func OpenStopRange(start, stop val.Tuple, desc val.TupleDesc) Range {
return Range{
Start: RangeCut{
Key: start,
Inclusive: true,
},
Stop: RangeCut{
Key: stop,
Inclusive: false,
},
KeyDesc: desc,
Start: inclusiveBound(start, desc),
Stop: exclusiveBound(stop, desc),
Desc: desc,
}
}
// ClosedRange defines an inclusive Range of Tuples from |lo| to |last|.
// ClosedRange defines an inclusive Range of Tuples from |start| to |stop|.
func ClosedRange(start, stop val.Tuple, desc val.TupleDesc) Range {
return Range{
Start: RangeCut{
Key: start,
Inclusive: true,
},
Stop: RangeCut{
Key: stop,
Inclusive: true,
},
KeyDesc: desc,
Start: inclusiveBound(start, desc),
Stop: inclusiveBound(stop, desc),
Desc: desc,
}
}
func inclusiveBound(tup val.Tuple, desc val.TupleDesc) (cut []RangeCut) {
cut = make([]RangeCut, len(desc.Types))
for i := range cut {
cut[i] = RangeCut{
Value: tup.GetField(i),
Inclusive: true,
}
}
return
}
func exclusiveBound(tup val.Tuple, desc val.TupleDesc) (cut []RangeCut) {
cut = inclusiveBound(tup, desc)
cut[len(cut)-1].Inclusive = false
return
}
func formatRange(r Range) string {
var sb strings.Builder
sb.WriteString("( ")
seenOne := false
for i, cut := range r.Start {
if seenOne {
sb.WriteString(", ")
}
seenOne = true
v := "-∞"
if cut.Value != nil {
v = r.Desc.FormatValue(i, cut.Value)
}
var op string
switch {
case cut.Null:
op, v = "==", "NULL"
case cut.Inclusive:
op = ">="
default:
op = ">"
}
sb.WriteString(fmt.Sprintf("tuple[%d] %s %s", i, op, v))
}
for i, cut := range r.Stop {
if seenOne {
sb.WriteString(", ")
}
seenOne = true
v := "∞"
if cut.Value != nil {
v = r.Desc.FormatValue(i, cut.Value)
}
var op string
switch {
case cut.Null:
op, v = "==", "NULL"
case cut.Inclusive:
op = "<="
default:
op = "<"
}
sb.WriteString(fmt.Sprintf("tuple[%d] %s %s", i, op, v))
}
sb.WriteString(" )")
return sb.String()
}

View File

@@ -0,0 +1,183 @@
// Copyright 2021 Dolthub, 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,
// 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 prolly
import (
"context"
"io"
"github.com/dolthub/dolt/go/store/val"
)
type MapRangeIter interface {
Next(ctx context.Context) (key, value val.Tuple, err error)
}
var _ MapRangeIter = &prollyRangeIter{}
var _ MapRangeIter = &MutableMapRangeIter{}
type rangeIter interface {
iterate(ctx context.Context) error
current() (key, value val.Tuple)
}
var _ rangeIter = emptyIter{}
var _ rangeIter = &prollyRangeIter{}
type emptyIter struct{}
func (e emptyIter) iterate(ctx context.Context) (err error) {
return
}
func (e emptyIter) current() (key, value val.Tuple) {
return
}
func NewMutableMapRangeIter(memory, prolly rangeIter, rng Range) MapRangeIter {
if memory == nil {
memory = emptyIter{}
}
if prolly == nil {
prolly = emptyIter{}
}
return MutableMapRangeIter{
memory: memory,
prolly: prolly,
rng: rng,
}
}
// MutableMapRangeIter iterates over a Range of Tuples.
type MutableMapRangeIter struct {
memory rangeIter
prolly rangeIter
rng Range
}
// Next returns the next pair of Tuples in the Range, or io.EOF if the iter is done.
func (it MutableMapRangeIter) Next(ctx context.Context) (key, value val.Tuple, err error) {
for {
mk, mv := it.memory.current()
pk, pv := it.prolly.current()
if mk == nil && pk == nil {
// range is exhausted
return nil, nil, io.EOF
}
cmp := it.compareKeys(pk, mk)
switch {
case cmp < 0:
key, value = pk, pv
if err = it.prolly.iterate(ctx); err != nil {
return nil, nil, err
}
case cmp > 0:
key, value = mk, mv
if err = it.memory.iterate(ctx); err != nil {
return nil, nil, err
}
case cmp == 0:
// |it.memory| wins ties
key, value = mk, mv
if err = it.memory.iterate(ctx); err != nil {
return nil, nil, err
}
if err = it.prolly.iterate(ctx); err != nil {
return nil, nil, err
}
}
if key != nil && value == nil {
continue // pending delete
}
return key, value, nil
}
}
func (it MutableMapRangeIter) currentKeys() (memKey, proKey val.Tuple) {
if it.memory != nil {
memKey, _ = it.memory.current()
}
if it.prolly != nil {
proKey, _ = it.prolly.current()
}
return
}
func (it MutableMapRangeIter) compareKeys(memKey, proKey val.Tuple) int {
if memKey == nil {
return 1
}
if proKey == nil {
return -1
}
return it.rng.Desc.Compare(memKey, proKey)
}
type prollyRangeIter struct {
// current tuple location
curr *nodeCursor
// non-inclusive range stop
stop *nodeCursor
}
func (it *prollyRangeIter) Next(ctx context.Context) (key, value val.Tuple, err error) {
if it.curr == nil {
return nil, nil, io.EOF
}
key = it.curr.nd.keys.GetSlice(it.curr.idx)
value = it.curr.nd.values.GetSlice(it.curr.idx)
_, err = it.curr.advance(ctx)
if err != nil {
return nil, nil, err
}
if it.curr.compare(it.stop) >= 0 {
// past the end of the range
it.curr = nil
}
return
}
func (it *prollyRangeIter) current() (key, value val.Tuple) {
// |it.curr| is set to nil when its range is exhausted
if it.curr != nil && it.curr.valid() {
key = it.curr.nd.keys.GetSlice(it.curr.idx)
value = it.curr.nd.values.GetSlice(it.curr.idx)
}
return
}
func (it *prollyRangeIter) iterate(ctx context.Context) (err error) {
_, err = it.curr.advance(ctx)
if err != nil {
return err
}
if it.curr.compare(it.stop) >= 0 {
// past the end of the range
it.curr = nil
}
return
}

View File

@@ -25,7 +25,7 @@ import (
"github.com/dolthub/dolt/go/store/val"
)
type rangeTest struct {
type rangeIterTest struct {
name string
testRange Range
expCount int
@@ -44,7 +44,7 @@ func testIterRange(t *testing.T, om orderedMap, tuples [][2]val.Tuple) {
}
start, stop := tuples[a][0], tuples[z][0]
tests := []rangeTest{
tests := []rangeIterTest{
// two-sided ranges
{
name: "OpenRange",
@@ -220,7 +220,7 @@ func getKeyPrefix(key val.Tuple, desc val.TupleDesc) (partial val.Tuple) {
func getExpectedRangeSize(rng Range, tuples [][2]val.Tuple) (sz int) {
for i := range tuples {
k := tuples[i][0]
if rng.insideStart(k) && rng.insideStop(k) {
if rng.AboveStart(k) && rng.BelowStop(k) {
sz++
}
}
@@ -237,18 +237,18 @@ func TestMapIterRange(t *testing.T) {
vd := val.NewTupleDescriptor()
tuples := []val.Tuple{
intTuple(1, 1), intTuple(),
intTuple(1, 2), intTuple(),
intTuple(1, 3), intTuple(),
intTuple(2, 1), intTuple(),
intTuple(2, 2), intTuple(),
intTuple(2, 3), intTuple(),
intTuple(3, 1), intTuple(),
intTuple(3, 2), intTuple(),
intTuple(3, 3), intTuple(),
intTuple(4, 1), intTuple(),
intTuple(4, 2), intTuple(),
intTuple(4, 3), intTuple(),
intTuple(1, 1), intTuple(), // 0
intTuple(1, 2), intTuple(), // 2
intTuple(1, 3), intTuple(), // 4
intTuple(2, 1), intTuple(), // 6
intTuple(2, 2), intTuple(), // 8
intTuple(2, 3), intTuple(), // 10
intTuple(3, 1), intTuple(), // 12
intTuple(3, 2), intTuple(), // 14
intTuple(3, 3), intTuple(), // 16
intTuple(4, 1), intTuple(), // 18
intTuple(4, 2), intTuple(), // 20
intTuple(4, 3), intTuple(), // 22
}
require.Equal(t, 24, len(tuples))
@@ -264,169 +264,105 @@ func TestMapIterRange(t *testing.T) {
val.Type{Enc: val.Int32Enc},
)
tests := []mapRangeTest{
tests := []struct {
name string
rng Range
inRange []val.Tuple
}{
// partial-key range scan
{
name: "range [1:4]",
rng: Range{
Start: RangeCut{
Key: intTuple(1),
Inclusive: true,
},
Stop: RangeCut{
Key: intTuple(4),
Inclusive: true,
},
KeyDesc: partialDesc,
},
exp: tuples[:],
name: "range [1:4]",
rng: ClosedRange(intTuple(1), intTuple(4), partialDesc),
inRange: tuples[:],
},
{
name: "range (1:4]",
rng: Range{
Start: RangeCut{
Key: intTuple(1),
Inclusive: false,
},
Stop: RangeCut{
Key: intTuple(4),
Inclusive: true,
},
KeyDesc: partialDesc,
},
exp: tuples[6:],
name: "range (1:4]",
rng: OpenStartRange(intTuple(1), intTuple(4), partialDesc),
inRange: tuples[6:],
},
{
name: "range [1:4)",
rng: Range{
Start: RangeCut{
Key: intTuple(1),
Inclusive: true,
},
Stop: RangeCut{
Key: intTuple(4),
Inclusive: false,
},
KeyDesc: partialDesc,
},
exp: tuples[:18],
name: "range [1:4)",
rng: OpenStopRange(intTuple(1), intTuple(4), partialDesc),
inRange: tuples[:18],
},
{
name: "range (1:4)",
rng: Range{
Start: RangeCut{
Key: intTuple(1),
Inclusive: false,
},
Stop: RangeCut{
Key: intTuple(4),
Inclusive: false,
},
KeyDesc: partialDesc,
},
exp: tuples[6:18],
name: "range (1:4)",
rng: OpenRange(intTuple(1), intTuple(4), partialDesc),
inRange: tuples[6:18],
},
// full-key range scan
{
name: "range [1,2:4,2]",
rng: Range{
Start: RangeCut{
Key: intTuple(1, 2),
Inclusive: true,
},
Stop: RangeCut{
Key: intTuple(4, 2),
Inclusive: true,
},
KeyDesc: fullDesc,
},
exp: tuples[2:22],
name: "range [1,2:4,2]",
rng: ClosedRange(intTuple(1, 2), intTuple(4, 2), fullDesc),
inRange: tuples[:],
},
{
name: "range (1,2:4,2]",
rng: Range{
Start: RangeCut{
Key: intTuple(1, 2),
Inclusive: false,
},
Stop: RangeCut{
Key: intTuple(4, 2),
Inclusive: true,
},
KeyDesc: fullDesc,
},
exp: tuples[4:22],
name: "range (1,2:4,2]",
rng: OpenStartRange(intTuple(1, 2), intTuple(4, 2), fullDesc),
inRange: tuples[:],
},
{
name: "range [1,2:4,2)",
rng: Range{
Start: RangeCut{
Key: intTuple(1, 2),
Inclusive: true,
},
Stop: RangeCut{
Key: intTuple(4, 2),
Inclusive: false,
},
KeyDesc: fullDesc,
},
exp: tuples[2:20],
name: "range [1,2:4,2)",
rng: OpenStopRange(intTuple(1, 2), intTuple(4, 2), fullDesc),
inRange: tuples[:],
},
{
name: "range (1,2:4,2)",
rng: Range{
Start: RangeCut{
Key: intTuple(1, 2),
Inclusive: false,
},
Stop: RangeCut{
Key: intTuple(4, 2),
Inclusive: false,
},
KeyDesc: fullDesc,
},
exp: tuples[4:20], // 🌲
name: "range (1,2:4,2)",
rng: OpenRange(intTuple(1, 2), intTuple(4, 2), fullDesc),
inRange: tuples[:],
},
{
name: "range [2,2:3,2]",
rng: ClosedRange(intTuple(2, 2), intTuple(3, 2), fullDesc),
inRange: tuples[6:18],
},
{
name: "range (2,2:3,2]",
rng: OpenStartRange(intTuple(2, 2), intTuple(3, 2), fullDesc),
inRange: tuples[6:18],
},
{
name: "range [2,2:3,2)",
rng: OpenStopRange(intTuple(2, 2), intTuple(3, 2), fullDesc),
inRange: tuples[6:18],
},
{
name: "range (2,2:3,2)",
rng: OpenRange(intTuple(2, 2), intTuple(3, 2), fullDesc),
inRange: tuples[6:18],
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
testMapRange(t, index, test)
ctx := context.Background()
iter, err := index.IterRange(ctx, test.rng)
require.NoError(t, err)
var k, v val.Tuple
act := make([]val.Tuple, 0, len(test.inRange))
for {
k, v, err = iter.Next(ctx)
if err == io.EOF {
break
}
assert.NoError(t, err)
act = append(act, k, v)
}
assert.Error(t, io.EOF, err)
assert.Equal(t, len(test.inRange), len(act))
if len(test.inRange) == len(act) {
for i := range test.inRange {
assert.Equal(t, test.inRange[i], act[i])
}
}
})
}
}
type mapRangeTest struct {
name string
rng Range
exp []val.Tuple
}
func testMapRange(t *testing.T, idx Map, test mapRangeTest) {
ctx := context.Background()
iter, err := idx.IterRange(ctx, test.rng)
require.NoError(t, err)
var k, v val.Tuple
act := make([]val.Tuple, 0, len(test.exp))
for {
k, v, err = iter.Next(ctx)
if err == io.EOF {
break
}
assert.NoError(t, err)
act = append(act, k, v)
}
assert.Error(t, io.EOF, err)
require.Equal(t, len(test.exp), len(act))
for i := range test.exp {
assert.Equal(t, test.exp[i], act[i])
}
}
func intTuple(ints ...int32) val.Tuple {
types := make([]val.Type, len(ints))
for i := range types {
@@ -440,3 +376,18 @@ func intTuple(ints ...int32) val.Tuple {
}
return tb.Build(sharedPool)
}
func concat(slices ...[]val.Tuple) (c []val.Tuple) {
var n int
for _, sl := range slices {
n += len(sl)
}
c = make([]val.Tuple, n)
n = 0
for _, sl := range slices {
copy(c[n:], sl)
n += len(sl)
}
return
}

View File

@@ -0,0 +1,206 @@
// Copyright 2022 Dolthub, 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,
// 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 prolly
import (
"context"
"encoding/binary"
"io"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/dolthub/dolt/go/store/val"
)
var tuples = []val.Tuple{
intTuple(1, 1), // 0
intTuple(1, 2), // 1
intTuple(1, 3), // 2
intTuple(2, 1), // 3
intTuple(2, 2), // 4
intTuple(2, 3), // 5
intTuple(3, 1), // 6
intTuple(3, 2), // 7
intTuple(3, 3), // 8
intTuple(4, 1), // 9
intTuple(4, 2), // 10
intTuple(4, 3), // 11
intNullTuple(&nine, nil), // 12
intNullTuple(nil, &nine), // 13
}
var nine = int32(9)
func TestRangeSearch(t *testing.T) {
intType := val.Type{Enc: val.Int32Enc}
twoCol := val.NewTupleDescriptor(
intType, // c0
intType, // c1
)
tests := []struct {
name string
testRange Range
hi, lo int
}{
{
name: "unbound range",
testRange: Range{
Start: nil,
Stop: nil,
Desc: twoCol,
},
lo: 0,
hi: 14,
},
// first column ranges
{
name: "c0 > 1",
testRange: Range{
Start: []RangeCut{
{Value: intVal(1), Inclusive: false},
},
Stop: nil,
Desc: twoCol,
},
lo: 3,
hi: 14,
},
{
name: "c0 < 1",
testRange: Range{
Start: nil,
Stop: []RangeCut{
{Value: intVal(1), Inclusive: false},
},
Desc: twoCol,
},
lo: 0,
hi: 0,
},
{
name: "2 <= c0 <= 3",
testRange: Range{
Start: []RangeCut{
{Value: intVal(2), Inclusive: true},
},
Stop: []RangeCut{
{Value: intVal(3), Inclusive: true},
},
Desc: twoCol,
},
lo: 3,
hi: 9,
},
{
name: "c0 = NULL",
testRange: Range{
Start: []RangeCut{
{Null: true},
},
Stop: []RangeCut{
{Null: true},
},
Desc: twoCol,
},
lo: 13,
hi: 14,
},
// second column ranges
{
name: "c1 == 2",
testRange: Range{
Start: []RangeCut{
{Value: nil},
{Value: intVal(2), Inclusive: true},
},
Stop: []RangeCut{
{Value: nil},
{Value: intVal(2), Inclusive: true},
},
Desc: twoCol,
},
lo: 0,
hi: 14,
},
}
values := make([]val.Tuple, len(tuples))
for i := range values {
values[i] = make(val.Tuple, 0)
}
testNode := newTupleLeafNode(tuples, values)
testMap := Map{root: testNode, keyDesc: twoCol}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
ctx := context.Background()
rng := test.testRange
startSearch := rangeStartSearchFn(rng)
idx := startSearch(testNode)
assert.Equal(t, test.lo, idx, "range should start at index %d", test.lo)
stopSearch := rangeStopSearchFn(rng)
idx = stopSearch(testNode)
assert.Equal(t, test.hi, idx, "range should stop before index %d", test.hi)
iter, err := testMap.IterRange(ctx, rng)
require.NoError(t, err)
expected := tuples[test.lo:test.hi]
i := 0
for {
tup, _, err := iter.Next(ctx)
if err == io.EOF {
break
}
require.NoError(t, err)
require.True(t, i < len(expected))
assert.Equal(t, expected[i], tup)
i++
}
})
}
}
func intVal(i int32) (buf []byte) {
buf = make([]byte, 4)
binary.LittleEndian.PutUint32(buf, uint32(i))
return
}
func intNullTuple(ints ...*int32) val.Tuple {
types := make([]val.Type, len(ints))
for i := range types {
types[i] = val.Type{
Enc: val.Int32Enc,
Nullable: true,
}
}
desc := val.NewTupleDescriptor(types...)
tb := val.NewTupleBuilder(desc)
for i, val := range ints {
if val != nil {
tb.PutInt32(i, *val)
}
}
return tb.Build(sharedPool)
}

View File

@@ -37,6 +37,8 @@ type List struct {
type ValueCmp func(left, right []byte) int
type SearchFn func(nodeKey []byte) bool
type nodeId uint32
type skipPointer [maxHeight]nodeId
@@ -177,12 +179,14 @@ func (it *ListIter) Retreat() {
}
func (l *List) GetIterAt(key []byte) (it *ListIter) {
return l.GetIterAtWithFn(key, l.cmp)
return l.GetIterFromSearchFn(func(nodeKey []byte) bool {
return l.compareKeysWithFn(key, nodeKey, l.cmp) > 0
})
}
func (l *List) GetIterAtWithFn(key []byte, cmp ValueCmp) (it *ListIter) {
func (l *List) GetIterFromSearchFn(kontinue SearchFn) (it *ListIter) {
it = &ListIter{
curr: l.seekWithFn(key, cmp),
curr: l.seekWithSearchFn(kontinue),
list: l,
}
@@ -211,14 +215,20 @@ func (l *List) IterAtEnd() *ListIter {
// seek returns the skipNode with the smallest key >= |key|.
func (l *List) seek(key []byte) skipNode {
return l.seekWithFn(key, l.cmp)
return l.seekWithCompare(key, l.cmp)
}
func (l *List) seekWithFn(key []byte, cmp ValueCmp) (node skipNode) {
func (l *List) seekWithCompare(key []byte, cmp ValueCmp) (node skipNode) {
return l.seekWithSearchFn(func(nodeKey []byte) bool {
return l.compareKeysWithFn(key, nodeKey, cmp) > 0
})
}
func (l *List) seekWithSearchFn(kontinue SearchFn) (node skipNode) {
ptr := l.head
for h := int64(highest); h >= 0; h-- {
node = l.getNode(ptr[h])
for l.compareKeysWithFn(key, node.key, cmp) > 0 {
for kontinue(node.key) {
ptr = node.next
node = l.getNode(ptr[h])
}

View File

@@ -409,61 +409,6 @@ func writeRaw(buf, val []byte) {
copy(buf, val)
}
func compare(typ Type, left, right []byte) int {
// order NULLs last
if left == nil {
if right == nil {
return 0
} else {
return 1
}
} else if right == nil {
if left == nil {
return 0
} else {
return -1
}
}
switch typ.Enc {
case Int8Enc:
return compareInt8(readInt8(left), readInt8(right))
case Uint8Enc:
return compareUint8(readUint8(left), readUint8(right))
case Int16Enc:
return compareInt16(readInt16(left), readInt16(right))
case Uint16Enc:
return compareUint16(readUint16(left), readUint16(right))
case Int32Enc:
return compareInt32(readInt32(left), readInt32(right))
case Uint32Enc:
return compareUint32(readUint32(left), readUint32(right))
case Int64Enc:
return compareInt64(readInt64(left), readInt64(right))
case Uint64Enc:
return compareUint64(readUint64(left), readUint64(right))
case Float32Enc:
return compareFloat32(readFloat32(left), readFloat32(right))
case Float64Enc:
return compareFloat64(readFloat64(left), readFloat64(right))
case YearEnc:
return compareInt16(readInt16(left), readInt16(right))
case DateEnc, DatetimeEnc, TimestampEnc:
return compareTimestamp(readTimestamp(left), readTimestamp(right))
case TimeEnc:
panic("unimplemented")
case DecimalEnc:
// todo(andy): temporary Decimal implementation
fallthrough
case StringEnc:
return compareString(readString(left), readString(right))
case ByteStringEnc:
return compareByteString(readByteString(left), readByteString(right))
default:
panic("unknown encoding")
}
}
func expectSize(buf []byte, sz ByteSize) {
if ByteSize(len(buf)) != sz {
panic("byte slice is not of expected size")

View File

@@ -53,8 +53,7 @@ func (tb *TupleBuilder) Build(pool pool.BuffPool) (tup Tuple) {
}
// BuildPermissive materializes a Tuple from the fields
// written to the TupleBuilder without checking nullability.
// todo(andy): restructure
// written to the TupleBuilder without validating nullability.
func (tb *TupleBuilder) BuildPermissive(pool pool.BuffPool) (tup Tuple) {
values := tb.fields[:tb.Desc.Count()]
tup = NewTuple(pool, values...)

View File

@@ -163,6 +163,8 @@ func testRoundTripInts(t *testing.T) {
type testCompare struct{}
var _ TupleComparator = testCompare{}
func (tc testCompare) Compare(left, right Tuple, desc TupleDesc) (cmp int) {
for i, typ := range desc.Types {
cmp = compare(typ, left.GetField(i), right.GetField(i))
@@ -173,4 +175,6 @@ func (tc testCompare) Compare(left, right Tuple, desc TupleDesc) (cmp int) {
return
}
var _ TupleComparator = testCompare{}
func (tc testCompare) CompareValues(left, right []byte, typ Type) int {
return compare(typ, left, right)
}

View File

@@ -0,0 +1,109 @@
// Copyright 2021 Dolthub, 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,
// 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 val
// TupleComparator compares Tuples.
type TupleComparator interface {
// Compare compares pairs of Tuples.
Compare(left, right Tuple, desc TupleDesc) int
// CompareValues compares pairs of values.
CompareValues(left, right []byte, typ Type) int
}
type defaultCompare struct{}
var _ TupleComparator = defaultCompare{}
// Compare implements TupleComparator
func (d defaultCompare) Compare(left, right Tuple, desc TupleDesc) (cmp int) {
for i := range desc.fast {
start, stop := desc.fast[i][0], desc.fast[i][1]
cmp = compare(desc.Types[i], left[start:stop], right[start:stop])
if cmp != 0 {
return cmp
}
}
off := len(desc.fast)
for i, typ := range desc.Types[off:] {
j := i + off
cmp = compare(typ, left.GetField(j), right.GetField(j))
if cmp != 0 {
return cmp
}
}
return
}
// CompareValues implements TupleComparator
func (d defaultCompare) CompareValues(left, right []byte, typ Type) (cmp int) {
return compare(typ, left, right)
}
func compare(typ Type, left, right []byte) int {
// order NULLs last
if left == nil {
if right == nil {
return 0
} else {
return 1
}
} else if right == nil {
if left == nil {
return 0
} else {
return -1
}
}
switch typ.Enc {
case Int8Enc:
return compareInt8(readInt8(left), readInt8(right))
case Uint8Enc:
return compareUint8(readUint8(left), readUint8(right))
case Int16Enc:
return compareInt16(readInt16(left), readInt16(right))
case Uint16Enc:
return compareUint16(readUint16(left), readUint16(right))
case Int32Enc:
return compareInt32(readInt32(left), readInt32(right))
case Uint32Enc:
return compareUint32(readUint32(left), readUint32(right))
case Int64Enc:
return compareInt64(readInt64(left), readInt64(right))
case Uint64Enc:
return compareUint64(readUint64(left), readUint64(right))
case Float32Enc:
return compareFloat32(readFloat32(left), readFloat32(right))
case Float64Enc:
return compareFloat64(readFloat64(left), readFloat64(right))
case YearEnc:
return compareInt16(readInt16(left), readInt16(right))
case DateEnc, DatetimeEnc, TimestampEnc:
return compareTimestamp(readTimestamp(left), readTimestamp(right))
case TimeEnc:
panic("unimplemented")
case DecimalEnc:
// todo(andy): temporary Decimal implementation
fallthrough
case StringEnc:
return compareString(readString(left), readString(right))
case ByteStringEnc:
return compareByteString(readByteString(left), readByteString(right))
default:
panic("unknown encoding")
}
}

View File

@@ -21,16 +21,16 @@ import (
"time"
)
// TupleDesc describes a Tuple set.
// Data structures that contain Tuples and algorithms that process Tuples
// use a TupleDesc's types to interpret the fields of a Tuple.
type TupleDesc struct {
Types []Type
cmp TupleComparator
fast fixedAccess
}
type TupleComparator interface {
Compare(left, right Tuple, desc TupleDesc) int
}
// NewTupleDescriptor makes a TupleDescriptor from |types|.
func NewTupleDescriptor(types ...Type) TupleDesc {
return NewTupleDescriptorWithComparator(defaultCompare{}, types...)
}
@@ -56,34 +56,6 @@ func NewTupleDescriptorWithComparator(cmp TupleComparator, types ...Type) (td Tu
return
}
func TupleDescriptorPrefix(td TupleDesc, count int) TupleDesc {
return NewTupleDescriptorWithComparator(td.cmp, td.Types[:count]...)
}
type defaultCompare struct{}
func (d defaultCompare) Compare(left, right Tuple, desc TupleDesc) (cmp int) {
for i := range desc.fast {
start, stop := desc.fast[i][0], desc.fast[i][1]
cmp = compare(desc.Types[i], left[start:stop], right[start:stop])
if cmp != 0 {
return cmp
}
}
off := len(desc.fast)
for i, typ := range desc.Types[off:] {
j := i + off
cmp = compare(typ, left.GetField(j), right.GetField(j))
if cmp != 0 {
return cmp
}
}
return
}
var _ TupleComparator = defaultCompare{}
type fixedAccess [][2]ByteSize
func makeFixedAccess(types []Type) (acc fixedAccess) {
@@ -104,11 +76,7 @@ func makeFixedAccess(types []Type) (acc fixedAccess) {
return
}
func (td TupleDesc) WithoutFixedAccess() TupleDesc {
td.fast = nil
return td
}
// GetField returns the ith field of |tup|.
func (td TupleDesc) GetField(i int, tup Tuple) []byte {
if i < len(td.fast) {
start, stop := td.fast[i][0], td.fast[i][1]
@@ -117,11 +85,28 @@ func (td TupleDesc) GetField(i int, tup Tuple) []byte {
return tup.GetField(i)
}
// Compare returns the Comparison of |left| and |right|.
// Compare compares |left| and |right|.
func (td TupleDesc) Compare(left, right Tuple) (cmp int) {
return td.cmp.Compare(left, right, td)
}
// CompareField compares |value| with the ith field of |tup|.
func (td TupleDesc) CompareField(value []byte, i int, tup Tuple) (cmp int) {
var v []byte
if i < len(td.fast) {
start, stop := td.fast[i][0], td.fast[i][1]
v = tup[start:stop]
} else {
v = tup.GetField(i)
}
return td.cmp.CompareValues(value, v, td.Types[i])
}
// Comparator returns the TupleDescriptor's TupleComparator.
func (td TupleDesc) Comparator() TupleComparator {
return td.cmp
}
// Count returns the number of fields in the TupleDesc.
func (td TupleDesc) Count() int {
return len(td.Types)
@@ -365,59 +350,72 @@ func (td TupleDesc) Format(tup Tuple) string {
sb.WriteString("( ")
seenOne := false
for i, typ := range td.Types {
for i := range td.Types {
if seenOne {
sb.WriteString(", ")
}
seenOne = true
if td.IsNull(i, tup) {
sb.WriteString("NULL")
continue
}
// todo(andy): complete cases
switch typ.Enc {
case Int8Enc:
v, _ := td.GetInt8(i, tup)
sb.WriteString(strconv.Itoa(int(v)))
case Uint8Enc:
v, _ := td.GetUint8(i, tup)
sb.WriteString(strconv.Itoa(int(v)))
case Int16Enc:
v, _ := td.GetInt16(i, tup)
sb.WriteString(strconv.Itoa(int(v)))
case Uint16Enc:
v, _ := td.GetUint16(i, tup)
sb.WriteString(strconv.Itoa(int(v)))
case Int32Enc:
v, _ := td.GetInt32(i, tup)
sb.WriteString(strconv.Itoa(int(v)))
case Uint32Enc:
v, _ := td.GetUint32(i, tup)
sb.WriteString(strconv.Itoa(int(v)))
case Int64Enc:
v, _ := td.GetInt64(i, tup)
sb.WriteString(strconv.FormatInt(v, 10))
case Uint64Enc:
v, _ := td.GetUint64(i, tup)
sb.WriteString(strconv.FormatUint(v, 10))
case Float32Enc:
v, _ := td.GetFloat32(i, tup)
sb.WriteString(fmt.Sprintf("%f", v))
case Float64Enc:
v, _ := td.GetFloat64(i, tup)
sb.WriteString(fmt.Sprintf("%f", v))
case StringEnc:
v, _ := td.GetString(i, tup)
sb.WriteString(v)
case ByteStringEnc:
v, _ := td.GetBytes(i, tup)
sb.Write(v)
default:
sb.Write(tup.GetField(i))
}
sb.WriteString(td.FormatValue(i, tup.GetField(i)))
}
sb.WriteString(" )")
return sb.String()
}
func (td TupleDesc) FormatValue(i int, value []byte) string {
if value == nil {
return "NULL"
}
// todo(andy): complete cases
switch td.Types[i].Enc {
case Int8Enc:
v := readInt8(value)
return strconv.Itoa(int(v))
case Uint8Enc:
v := readUint8(value)
return strconv.Itoa(int(v))
case Int16Enc:
v := readInt16(value)
return strconv.Itoa(int(v))
case Uint16Enc:
v := readUint16(value)
return strconv.Itoa(int(v))
case Int32Enc:
v := readInt32(value)
return strconv.Itoa(int(v))
case Uint32Enc:
v := readUint32(value)
return strconv.Itoa(int(v))
case Int64Enc:
v := readInt64(value)
return strconv.FormatInt(v, 10)
case Uint64Enc:
v := readUint64(value)
return strconv.FormatUint(v, 10)
case Float32Enc:
v := readFloat32(value)
return fmt.Sprintf("%f", v)
case Float64Enc:
v := readFloat64(value)
return fmt.Sprintf("%f", v)
case StringEnc:
return readString(value)
case ByteStringEnc:
return string(value)
default:
return string(value)
}
}
// Equals returns true if |td| and |other| have equal type slices.
func (td TupleDesc) Equals(other TupleDesc) bool {
if len(td.Types) != len(other.Types) {
return false
}
for i, typ := range td.Types {
if typ != other.Types[i] {
return false
}
}
return true
}