mirror of
https://github.com/dolthub/dolt.git
synced 2026-02-25 00:54:51 -06:00
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:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
183
go/store/prolly/range_iter.go
Normal file
183
go/store/prolly/range_iter.go
Normal 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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
206
go/store/prolly/range_test.go
Normal file
206
go/store/prolly/range_test.go
Normal 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)
|
||||
}
|
||||
@@ -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])
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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...)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
109
go/store/val/tuple_compare.go
Normal file
109
go/store/val/tuple_compare.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user