mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-10 15:01:03 +00:00
Compare commits
5 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b8ef96e523 | ||
|
|
bead79a816 | ||
|
|
0e4d0cbc28 | ||
|
|
5ebd7e8da8 | ||
|
|
5d71f04e54 |
7 changed files with 190 additions and 43 deletions
|
|
@ -5,6 +5,13 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
||||||
and this project adheres to [Semantic Versioning](http://semver.org/).
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
||||||
|
|
||||||
|
## [1.4.1] - 2020-10-08
|
||||||
|
|
||||||
|
This version contains 1 contribution from 1 contributor. There are 4 files changed; 170 insertions; and 30 deletions.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Add logic that clears fragment.rowCache after importValue ([#2084](https://github.com/pilosa/pilosa/pull/2084))
|
||||||
|
|
||||||
## [1.4.0] - 2019-09-17
|
## [1.4.0] - 2019-09-17
|
||||||
|
|
||||||
This version contains 99 contributions from 11 contributors. There are 94 files changed; 9,453 insertions; and 6,121 deletions.
|
This version contains 99 contributions from 11 contributors. There are 94 files changed; 9,453 insertions; and 6,121 deletions.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
FROM golang:1.13.0 as builder
|
FROM golang:1.15.0 as builder
|
||||||
|
|
||||||
COPY . pilosa
|
COPY . pilosa
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@ There are four ways to install Pilosa on MacOS: Use [Homebrew](https://brew.sh/)
|
||||||
backing up, and more. Complete documentation is available
|
backing up, and more. Complete documentation is available
|
||||||
at https://www.pilosa.com/docs/.
|
at https://www.pilosa.com/docs/.
|
||||||
|
|
||||||
Version: v1.4.0
|
Version: v1.4.1
|
||||||
Build Time: 2018-05-14T22:14:01+0000
|
Build Time: 2018-05-14T22:14:01+0000
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
@ -71,19 +71,19 @@ There are four ways to install Pilosa on MacOS: Use [Homebrew](https://brew.sh/)
|
||||||
|
|
||||||
1. Download the latest release:
|
1. Download the latest release:
|
||||||
```
|
```
|
||||||
curl -L -O https://github.com/pilosa/pilosa/releases/download/v1.4.0/pilosa-v1.4.0-darwin-amd64.tar.gz
|
curl -L -O https://github.com/pilosa/pilosa/releases/download/v1.4.1/pilosa-v1.4.1-darwin-amd64.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
Other releases can be downloaded from our Releases page on Github.
|
Other releases can be downloaded from our Releases page on Github.
|
||||||
|
|
||||||
2. Extract the binary:
|
2. Extract the binary:
|
||||||
```
|
```
|
||||||
tar xfz pilosa-v1.4.0-darwin-amd64.tar.gz
|
tar xfz pilosa-v1.4.1-darwin-amd64.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Move the binary into your PATH so you can run `pilosa` from any shell:
|
3. Move the binary into your PATH so you can run `pilosa` from any shell:
|
||||||
```
|
```
|
||||||
cp -i pilosa-v1.4.0-darwin-amd64/pilosa /usr/local/bin
|
cp -i pilosa-v1.4.1-darwin-amd64/pilosa /usr/local/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Make sure Pilosa is installed successfully:
|
4. Make sure Pilosa is installed successfully:
|
||||||
|
|
@ -100,7 +100,7 @@ There are four ways to install Pilosa on MacOS: Use [Homebrew](https://brew.sh/)
|
||||||
backing up, and more. Complete documentation is available
|
backing up, and more. Complete documentation is available
|
||||||
at https://www.pilosa.com/docs/.
|
at https://www.pilosa.com/docs/.
|
||||||
|
|
||||||
Version: v1.4.0
|
Version: v1.4.1
|
||||||
Build Time: 2018-05-14T22:14:01+0000
|
Build Time: 2018-05-14T22:14:01+0000
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
@ -163,7 +163,7 @@ There are four ways to install Pilosa on MacOS: Use [Homebrew](https://brew.sh/)
|
||||||
backing up, and more. Complete documentation is available
|
backing up, and more. Complete documentation is available
|
||||||
at https://www.pilosa.com/docs/.
|
at https://www.pilosa.com/docs/.
|
||||||
|
|
||||||
Version: v1.4.0
|
Version: v1.4.1
|
||||||
Build Time: 2018-05-14T22:14:01+0000
|
Build Time: 2018-05-14T22:14:01+0000
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
@ -201,19 +201,19 @@ There are three ways to install Pilosa on Linux: download the binary (recommende
|
||||||
|
|
||||||
1. To install the latest version of Pilosa, download the latest release:
|
1. To install the latest version of Pilosa, download the latest release:
|
||||||
```
|
```
|
||||||
curl -L -O https://github.com/pilosa/pilosa/releases/download/v1.4.0/pilosa-v1.4.0-linux-amd64.tar.gz
|
curl -L -O https://github.com/pilosa/pilosa/releases/download/v1.4.1/pilosa-v1.4.1-linux-amd64.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
Note: This assumes you are using an `amd64` compatible architecture. Other releases can be downloaded from our Releases page on Github.
|
Note: This assumes you are using an `amd64` compatible architecture. Other releases can be downloaded from our Releases page on Github.
|
||||||
|
|
||||||
2. Extract the binary:
|
2. Extract the binary:
|
||||||
```
|
```
|
||||||
tar xfz pilosa-v1.4.0-linux-amd64.tar.gz
|
tar xfz pilosa-v1.4.1-linux-amd64.tar.gz
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Move the binary into your PATH so you can run `pilosa` from any shell:
|
3. Move the binary into your PATH so you can run `pilosa` from any shell:
|
||||||
```
|
```
|
||||||
cp -i pilosa-v1.4.0-linux-amd64/pilosa /usr/local/bin
|
cp -i pilosa-v1.4.1-linux-amd64/pilosa /usr/local/bin
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Make sure Pilosa is installed successfully:
|
4. Make sure Pilosa is installed successfully:
|
||||||
|
|
@ -230,7 +230,7 @@ There are three ways to install Pilosa on Linux: download the binary (recommende
|
||||||
backing up, and more. Complete documentation is available
|
backing up, and more. Complete documentation is available
|
||||||
at https://www.pilosa.com/docs/.
|
at https://www.pilosa.com/docs/.
|
||||||
|
|
||||||
Version: v1.4.0
|
Version: v1.4.1
|
||||||
Build Time: 2018-05-14T22:14:01+0000
|
Build Time: 2018-05-14T22:14:01+0000
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
@ -293,7 +293,7 @@ There are three ways to install Pilosa on Linux: download the binary (recommende
|
||||||
backing up, and more. Complete documentation is available
|
backing up, and more. Complete documentation is available
|
||||||
at https://www.pilosa.com/docs/.
|
at https://www.pilosa.com/docs/.
|
||||||
|
|
||||||
Version: v1.4.0
|
Version: v1.4.1
|
||||||
Build Time: 2018-05-14T22:14:01+0000
|
Build Time: 2018-05-14T22:14:01+0000
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
|
|
|
||||||
58
field.go
58
field.go
|
|
@ -1269,34 +1269,12 @@ func (f *Field) importValue(columnIDs []uint64, values []int64, options *ImportO
|
||||||
return errors.Wrap(ErrBSIGroupNotFound, f.name)
|
return errors.Wrap(ErrBSIGroupNotFound, f.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find the lowest/highest values.
|
// We want to determine the required bit depth, in case the field doesn't
|
||||||
|
// have as many bits currently as would be needed to represent these values,
|
||||||
|
// but only if the values are in-range for the field.
|
||||||
var min, max int64
|
var min, max int64
|
||||||
for i, value := range values {
|
if len(values) > 0 {
|
||||||
if i == 0 || value < min {
|
min, max = values[0], values[0]
|
||||||
min = value
|
|
||||||
}
|
|
||||||
if i == 0 || value > max {
|
|
||||||
max = value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine the highest bit depth required by the min & max.
|
|
||||||
requiredDepth := bitDepthInt64(min - bsig.Base)
|
|
||||||
if v := bitDepthInt64(max - bsig.Base); v > requiredDepth {
|
|
||||||
requiredDepth = v
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increase bit depth if required.
|
|
||||||
if requiredDepth > bsig.BitDepth {
|
|
||||||
if err := func() error {
|
|
||||||
f.mu.Lock()
|
|
||||||
defer f.mu.Unlock()
|
|
||||||
bsig.BitDepth = requiredDepth
|
|
||||||
f.options.BitDepth = requiredDepth
|
|
||||||
return f.saveMeta()
|
|
||||||
}(); err != nil {
|
|
||||||
return errors.Wrap(err, "increasing bsi bit depth")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Split import data by fragment.
|
// Split import data by fragment.
|
||||||
|
|
@ -1308,6 +1286,12 @@ func (f *Field) importValue(columnIDs []uint64, values []int64, options *ImportO
|
||||||
} else if value < bsig.Min {
|
} else if value < bsig.Min {
|
||||||
return fmt.Errorf("%v, columnID=%v, value=%v", ErrBSIGroupValueTooLow, columnID, value)
|
return fmt.Errorf("%v, columnID=%v, value=%v", ErrBSIGroupValueTooLow, columnID, value)
|
||||||
}
|
}
|
||||||
|
if value > max {
|
||||||
|
max = value
|
||||||
|
}
|
||||||
|
if value < min {
|
||||||
|
min = value
|
||||||
|
}
|
||||||
|
|
||||||
// Attach value to each bsiGroup view.
|
// Attach value to each bsiGroup view.
|
||||||
for _, name := range []string{viewName} {
|
for _, name := range []string{viewName} {
|
||||||
|
|
@ -1319,6 +1303,26 @@ func (f *Field) importValue(columnIDs []uint64, values []int64, options *ImportO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Determine the highest bit depth required by the min & max.
|
||||||
|
requiredDepth := bitDepthInt64(min - bsig.Base)
|
||||||
|
if v := bitDepthInt64(max - bsig.Base); v > requiredDepth {
|
||||||
|
requiredDepth = v
|
||||||
|
}
|
||||||
|
// Increase bit depth if required.
|
||||||
|
if requiredDepth > bsig.BitDepth {
|
||||||
|
if err := func() error {
|
||||||
|
f.mu.Lock()
|
||||||
|
defer f.mu.Unlock()
|
||||||
|
bsig.BitDepth = requiredDepth
|
||||||
|
f.options.BitDepth = requiredDepth
|
||||||
|
return f.saveMeta()
|
||||||
|
}(); err != nil {
|
||||||
|
return errors.Wrap(err, "increasing bsi bit depth")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
requiredDepth = bsig.BitDepth
|
||||||
|
}
|
||||||
|
|
||||||
// Import into each fragment.
|
// Import into each fragment.
|
||||||
for key, data := range dataByFragment {
|
for key, data := range dataByFragment {
|
||||||
// The view must already exist (i.e. we can't create it)
|
// The view must already exist (i.e. we can't create it)
|
||||||
|
|
|
||||||
10
fragment.go
10
fragment.go
|
|
@ -2191,9 +2191,16 @@ func (f *fragment) importValueSmallWrite(columnIDs []uint64, values []int64, bit
|
||||||
rowSet[uint64(i)] = struct{}{}
|
rowSet[uint64(i)] = struct{}{}
|
||||||
}
|
}
|
||||||
err := f.importPositions(toSet, toClear, rowSet)
|
err := f.importPositions(toSet, toClear, rowSet)
|
||||||
|
if err != nil {
|
||||||
return errors.Wrap(err, "importing positions")
|
return errors.Wrap(err, "importing positions")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset the rowCache.
|
||||||
|
f.rowCache = &simpleCache{make(map[uint64]*Row)}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// importValue bulk imports a set of range-encoded values.
|
// importValue bulk imports a set of range-encoded values.
|
||||||
func (f *fragment) importValue(columnIDs []uint64, values []int64, bitDepth uint, clear bool) error {
|
func (f *fragment) importValue(columnIDs []uint64, values []int64, bitDepth uint, clear bool) error {
|
||||||
f.mu.Lock()
|
f.mu.Lock()
|
||||||
|
|
@ -2230,6 +2237,9 @@ func (f *fragment) importValue(columnIDs []uint64, values []int64, bitDepth uint
|
||||||
// We don't actually care, except we want our stats to be accurate.
|
// We don't actually care, except we want our stats to be accurate.
|
||||||
f.incrementOpN(totalChanges)
|
f.incrementOpN(totalChanges)
|
||||||
|
|
||||||
|
// Reset the rowCache.
|
||||||
|
f.rowCache = &simpleCache{make(map[uint64]*Row)}
|
||||||
|
|
||||||
// in theory, this should probably have happened anyway, but if enough
|
// in theory, this should probably have happened anyway, but if enough
|
||||||
// of the bits matched existing bits, we'll be under our opN estimate, and
|
// of the bits matched existing bits, we'll be under our opN estimate, and
|
||||||
// we want to ensure that the snapshot happens.
|
// we want to ensure that the snapshot happens.
|
||||||
|
|
|
||||||
|
|
@ -3363,7 +3363,7 @@ func TestImportMultipleValues(t *testing.T) {
|
||||||
cols []uint64
|
cols []uint64
|
||||||
vals []int64
|
vals []int64
|
||||||
checkCols []uint64
|
checkCols []uint64
|
||||||
checkVals []uint64
|
checkVals []int64
|
||||||
depth uint
|
depth uint
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
@ -3371,7 +3371,7 @@ func TestImportMultipleValues(t *testing.T) {
|
||||||
vals: []int64{97, 100},
|
vals: []int64{97, 100},
|
||||||
depth: 7,
|
depth: 7,
|
||||||
checkCols: []uint64{0},
|
checkCols: []uint64{0},
|
||||||
checkVals: []uint64{100},
|
checkVals: []int64{100},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3395,7 +3395,7 @@ func TestImportMultipleValues(t *testing.T) {
|
||||||
if !exists {
|
if !exists {
|
||||||
t.Errorf("column %d should exist", cc)
|
t.Errorf("column %d should exist", cc)
|
||||||
}
|
}
|
||||||
if n != 100 {
|
if n != cv {
|
||||||
t.Errorf("wrong value: %d is not %d", n, cv)
|
t.Errorf("wrong value: %d is not %d", n, cv)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -3405,6 +3405,66 @@ func TestImportMultipleValues(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestImportValueRowCache(t *testing.T) {
|
||||||
|
type testCase struct {
|
||||||
|
cols []uint64
|
||||||
|
vals []int64
|
||||||
|
checkCols []uint64
|
||||||
|
depth uint
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
tc1 testCase
|
||||||
|
tc2 testCase
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
tc1: testCase{
|
||||||
|
cols: []uint64{2},
|
||||||
|
vals: []int64{1},
|
||||||
|
depth: 1,
|
||||||
|
checkCols: []uint64{2},
|
||||||
|
},
|
||||||
|
tc2: testCase{
|
||||||
|
cols: []uint64{1000},
|
||||||
|
vals: []int64{1},
|
||||||
|
depth: 1,
|
||||||
|
checkCols: []uint64{2, 1000},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
for _, maxOpN := range []int{1, 10000} {
|
||||||
|
t.Run(fmt.Sprintf("%dMaxOpN%d", i, maxOpN), func(t *testing.T) {
|
||||||
|
f := mustOpenBSIFragment("i", "f", viewBSIGroupPrefix+"foo", 0)
|
||||||
|
f.MaxOpN = maxOpN
|
||||||
|
defer f.Clean(t)
|
||||||
|
|
||||||
|
// First import (tc1)
|
||||||
|
if err := f.importValue(test.tc1.cols, test.tc1.vals, test.tc1.depth, false); err != nil {
|
||||||
|
t.Fatalf("importing values: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r, err := f.rangeOp(pql.GT, test.tc1.depth, 0); err != nil {
|
||||||
|
t.Error("getting range of values")
|
||||||
|
} else if !reflect.DeepEqual(r.Columns(), test.tc1.checkCols) {
|
||||||
|
t.Errorf("wrong column values. expected: %v, but got: %v", test.tc1.checkCols, r.Columns())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second import (tc2)
|
||||||
|
if err := f.importValue(test.tc2.cols, test.tc2.vals, test.tc2.depth, false); err != nil {
|
||||||
|
t.Fatalf("importing values: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if r, err := f.rangeOp(pql.GT, test.tc2.depth, 0); err != nil {
|
||||||
|
t.Error("getting range of values")
|
||||||
|
} else if !reflect.DeepEqual(r.Columns(), test.tc2.checkCols) {
|
||||||
|
t.Errorf("wrong column values. expected: %v, but got: %v", test.tc2.checkCols, r.Columns())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFragmentConcurrentReadWrite(t *testing.T) {
|
func TestFragmentConcurrentReadWrite(t *testing.T) {
|
||||||
f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked)
|
f := mustOpenFragment("i", "f", viewStandard, 0, CacheTypeRanked)
|
||||||
defer f.Clean(t)
|
defer f.Clean(t)
|
||||||
|
|
|
||||||
|
|
@ -777,6 +777,72 @@ func TestClient_ImportKeys(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestClient_ImportIDs(t *testing.T) {
|
||||||
|
// Ensure that running a query between two imports does
|
||||||
|
// not affect the result set. It turns out, this is caused
|
||||||
|
// by the fragment.rowCache failing to be cleared after an
|
||||||
|
// importValue. This ensures that the rowCache is cleared
|
||||||
|
// after an import.
|
||||||
|
t.Run("ImportRangeImport", func(t *testing.T) {
|
||||||
|
cluster := test.MustRunCluster(t, 1)
|
||||||
|
defer cluster.Close()
|
||||||
|
cmd := cluster[0]
|
||||||
|
host := cmd.URL()
|
||||||
|
holder := cmd.Server.Holder()
|
||||||
|
hldr := test.Holder{Holder: holder}
|
||||||
|
|
||||||
|
idxName := "i"
|
||||||
|
fldName := "f"
|
||||||
|
|
||||||
|
// Load bitmap into cache to ensure cache gets updated.
|
||||||
|
index := hldr.MustCreateIndexIfNotExists(idxName, pilosa.IndexOptions{Keys: false})
|
||||||
|
_, err := index.CreateFieldIfNotExists(fldName, pilosa.OptFieldTypeInt(-10000, 10000))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send import request.
|
||||||
|
c := MustNewClient(host, http.GetHTTPClient(nil))
|
||||||
|
if err := c.ImportValue(context.Background(), idxName, fldName, 0, []pilosa.FieldValue{
|
||||||
|
{ColumnID: 2, Value: 1},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify range.
|
||||||
|
queryRequest := &pilosa.QueryRequest{
|
||||||
|
Query: fmt.Sprintf(`Row(%s>0)`, fldName),
|
||||||
|
Remote: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
if result, err := c.Query(context.Background(), idxName, queryRequest); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
res := result.Results[0].(*pilosa.Row).Columns()
|
||||||
|
if !reflect.DeepEqual(res, []uint64{2}) {
|
||||||
|
t.Fatalf("unexpected column ids: %v", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send import request.
|
||||||
|
if err := c.ImportValue(context.Background(), idxName, fldName, 0, []pilosa.FieldValue{
|
||||||
|
{ColumnID: 1000, Value: 1},
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify range.
|
||||||
|
if result, err := c.Query(context.Background(), idxName, queryRequest); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
} else {
|
||||||
|
res := result.Results[0].(*pilosa.Row).Columns()
|
||||||
|
if !reflect.DeepEqual(res, []uint64{2, 1000}) {
|
||||||
|
t.Fatalf("unexpected column ids: %v", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure client can bulk import value data.
|
// Ensure client can bulk import value data.
|
||||||
func TestClient_ImportValue(t *testing.T) {
|
func TestClient_ImportValue(t *testing.T) {
|
||||||
cluster := test.MustRunCluster(t, 1)
|
cluster := test.MustRunCluster(t, 1)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue