featurebase/db_test.go
Ben Johnson eff08af112 Separate physical data layout with views.
Previously, multiple frames with different prefixes were used to separate
different data layouts. This included separating standard row/column
layouts from inverted column/row layouts as well as storing aggregate
information for timestamp data.

Unfortunately, this caused frame meta data to be copied between multiple
frames and it made it difficult to keep these frames in sync.

This commit separates these different physical layouts into `Views`.
A `Frame` now has one or more views which represent each layout.
Fragments have been moved from under the `Frame` to be contained
within the `View`.

There are two primary views:

- `standard`
- `inverse`

If a frame has a time quantum, then views are generated for these
each of the standard/inverse views. For example a time quantum
of `YMDH` for the date `2000-01-02T00:00:00Z` would create the
following views:

- `standard_2000`
- `inverse_2000`
- `standard_200001`
- `inverse_200001`
- `standard_20000102`
- `inverse_20000102`

From the user's perspective, nothing should change in PQL. Different
PQL statements will handle the appropriate view automatically. For
example, `Bitmap()` and `Profile()` will fetch using the `standard`
view or the `inverse` view, respectively. The `Range()` statement
will lookup the appropriate time-based views automatically.
2017-03-24 13:57:02 -06:00

160 lines
3.5 KiB
Go

package pilosa_test
import (
"io/ioutil"
"os"
"testing"
"github.com/pilosa/pilosa"
)
// Ensure database can open and retrieve a frame.
func TestDB_CreateFrameIfNotExists(t *testing.T) {
db := MustOpenDB()
defer db.Close()
// Create frame.
f, err := db.CreateFrameIfNotExists("f", pilosa.FrameOptions{})
if err != nil {
t.Fatal(err)
} else if f == nil {
t.Fatal("expected frame")
}
// Retrieve existing frame.
other, err := db.CreateFrameIfNotExists("f", pilosa.FrameOptions{})
if err != nil {
t.Fatal(err)
} else if f.Frame != other.Frame {
t.Fatal("frame mismatch")
}
if f.Frame != db.Frame("f") {
t.Fatal("frame mismatch")
}
}
// Ensure database can delete a frame.
func TestDB_DeleteFrame(t *testing.T) {
db := MustOpenDB()
defer db.Close()
// Create frame.
if _, err := db.CreateFrameIfNotExists("f", pilosa.FrameOptions{}); err != nil {
t.Fatal(err)
}
// Delete frame & verify it's gone.
if err := db.DeleteFrame("f"); err != nil {
t.Fatal(err)
} else if db.Frame("f") != nil {
t.Fatal("expected nil frame")
}
// Delete again to make sure it doesn't error.
if err := db.DeleteFrame("f"); err != nil {
t.Fatal(err)
}
}
// Ensure database can set the default time quantum.
func TestDB_SetTimeQuantum(t *testing.T) {
db := MustOpenDB()
defer db.Close()
// Set & retrieve time quantum.
if err := db.SetTimeQuantum(pilosa.TimeQuantum("YMDH")); err != nil {
t.Fatal(err)
} else if q := db.TimeQuantum(); q != pilosa.TimeQuantum("YMDH") {
t.Fatalf("unexpected quantum: %s", q)
}
// Reload database and verify that it is persisted.
if err := db.Reopen(); err != nil {
t.Fatal(err)
} else if q := db.TimeQuantum(); q != pilosa.TimeQuantum("YMDH") {
t.Fatalf("unexpected quantum (reopen): %s", q)
}
}
// DB represents a test wrapper for pilosa.DB.
type DB struct {
*pilosa.DB
}
// NewDB returns a new instance of DB d.
func NewDB() *DB {
path, err := ioutil.TempDir("", "pilosa-db-")
if err != nil {
panic(err)
}
db, err := pilosa.NewDB(path, "d")
if err != nil {
panic(err)
}
return &DB{DB: db}
}
// MustOpenDB returns a new, opened database at a temporary path. Panic on error.
func MustOpenDB() *DB {
db := NewDB()
if err := db.Open(); err != nil {
panic(err)
}
return db
}
// Close closes the database and removes the underlying data.
func (db *DB) Close() error {
defer os.RemoveAll(db.Path())
return db.DB.Close()
}
// Reopen closes the database and reopens it.
func (db *DB) Reopen() error {
var err error
if err := db.DB.Close(); err != nil {
return err
}
path, name := db.Path(), db.Name()
db.DB, err = pilosa.NewDB(path, name)
if err != nil {
return err
}
if err := db.Open(); err != nil {
return err
}
return nil
}
// CreateFrame creates a frame with the given options.
func (db *DB) CreateFrame(name string, opt pilosa.FrameOptions) (*Frame, error) {
f, err := db.DB.CreateFrame(name, opt)
if err != nil {
return nil, err
}
return &Frame{Frame: f}, nil
}
// CreateFrameIfNotExists creates a frame with the given options if it doesn't exist.
func (db *DB) CreateFrameIfNotExists(name string, opt pilosa.FrameOptions) (*Frame, error) {
f, err := db.DB.CreateFrameIfNotExists(name, opt)
if err != nil {
return nil, err
}
return &Frame{Frame: f}, nil
}
// Ensure database can delete a frame.
func TestDB_InvalidName(t *testing.T) {
path, err := ioutil.TempDir("", "pilosa-db-")
if err != nil {
panic(err)
}
db, err := pilosa.NewDB(path, "ABC")
if db != nil {
t.Fatalf("unexpected db name %s", db)
}
}