package tsdb_test

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"math"
	"math/rand"
	"os"
	"path/filepath"
	"reflect"
	"regexp"
	"sort"
	"strings"
	"testing"
	"time"

	"github.com/davecgh/go-spew/spew"
	"github.com/influxdata/influxdb/influxql"
	"github.com/influxdata/influxdb/models"
	"github.com/influxdata/influxdb/pkg/deep"
	"github.com/influxdata/influxdb/tsdb"
	"github.com/uber-go/zap"
)

// Ensure the store can delete a retention policy and all shards under
// it.
func TestStore_DeleteRetentionPolicy(t *testing.T) {
	t.Parallel()

	s := MustOpenStore()
	defer s.Close()

	// Create a new shard and verify that it exists.
	if err := s.CreateShard("db0", "rp0", 1, true); err != nil {
		t.Fatal(err)
	} else if sh := s.Shard(1); sh == nil {
		t.Fatalf("expected shard")
	}

	// Create a new shard under the same retention policy,  and verify
	// that it exists.
	if err := s.CreateShard("db0", "rp0", 2, true); err != nil {
		t.Fatal(err)
	} else if sh := s.Shard(2); sh == nil {
		t.Fatalf("expected shard")
	}

	// Create a new shard under a different retention policy, and
	// verify that it exists.
	if err := s.CreateShard("db0", "rp1", 3, true); err != nil {
		t.Fatal(err)
	} else if sh := s.Shard(3); sh == nil {
		t.Fatalf("expected shard")
	}

	// Deleting the rp0 retention policy does not return an error.
	if err := s.DeleteRetentionPolicy("db0", "rp0"); err != nil {
		t.Fatal(err)
	}

	// It deletes the shards under that retention policy.
	if sh := s.Shard(1); sh != nil {
		t.Errorf("shard 1 was not deleted")
	}

	if sh := s.Shard(2); sh != nil {
		t.Errorf("shard 2 was not deleted")
	}

	// It deletes the retention policy directory.
	if got, exp := dirExists(filepath.Join(s.Path(), "db0", "rp0")), false; got != exp {
		t.Error("directory exists, but should have been removed")
	}

	// It deletes the WAL retention policy directory.
	if got, exp := dirExists(filepath.Join(s.EngineOptions.Config.WALDir, "db0", "rp0")), false; got != exp {
		t.Error("directory exists, but should have been removed")
	}

	// Reopen other shard and check it still exists.
	if err := s.Reopen(); err != nil {
		t.Error(err)
	} else if sh := s.Shard(3); sh == nil {
		t.Errorf("shard 3 does not exist")
	}

	// It does not delete other retention policy directories.
	if got, exp := dirExists(filepath.Join(s.Path(), "db0", "rp1")), true; got != exp {
		t.Error("directory does not exist, but should")
	}
	if got, exp := dirExists(filepath.Join(s.EngineOptions.Config.WALDir, "db0", "rp1")), true; got != exp {
		t.Error("directory does not exist, but should")
	}
}

// Ensure the store can create a new shard.
func TestStore_CreateShard(t *testing.T) {
	t.Parallel()

	s := MustOpenStore()
	defer s.Close()

	// Create a new shard and verify that it exists.
	if err := s.CreateShard("db0", "rp0", 1, true); err != nil {
		t.Fatal(err)
	} else if sh := s.Shard(1); sh == nil {
		t.Fatalf("expected shard")
	}

	// Create another shard and verify that it exists.
	if err := s.CreateShard("db0", "rp0", 2, true); err != nil {
		t.Fatal(err)
	} else if sh := s.Shard(2); sh == nil {
		t.Fatalf("expected shard")
	}

	// Reopen shard and recheck.
	if err := s.Reopen(); err != nil {
		t.Fatal(err)
	} else if sh := s.Shard(1); sh == nil {
		t.Fatalf("expected shard(1)")
	} else if sh = s.Shard(2); sh == nil {
		t.Fatalf("expected shard(2)")
	}
}

// Ensure the store can delete an existing shard.
func TestStore_DeleteShard(t *testing.T) {
	t.Parallel()

	s := MustOpenStore()
	defer s.Close()

	// Create a new shard and verify that it exists.
	if err := s.CreateShard("db0", "rp0", 1, true); err != nil {
		t.Fatal(err)
	} else if sh := s.Shard(1); sh == nil {
		t.Fatalf("expected shard")
	}

	// Reopen shard and recheck.
	if err := s.Reopen(); err != nil {
		t.Fatal(err)
	} else if sh := s.Shard(1); sh == nil {
		t.Fatalf("shard exists")
	}
}

// Ensure the store can create a snapshot to a shard.
func TestStore_CreateShardSnapShot(t *testing.T) {
	t.Parallel()

	s := MustOpenStore()
	defer s.Close()

	// Create a new shard and verify that it exists.
	if err := s.CreateShard("db0", "rp0", 1, true); err != nil {
		t.Fatal(err)
	} else if sh := s.Shard(1); sh == nil {
		t.Fatalf("expected shard")
	}

	dir, e := s.CreateShardSnapshot(1)
	if e != nil {
		t.Fatal(e)
	}
	if dir == "" {
		t.Fatal("empty directory name")
	}
}

func TestStore_Open(t *testing.T) {
	t.Parallel()

	s := NewStore()
	defer s.Close()

	if err := os.MkdirAll(filepath.Join(s.Path(), "db0", "rp0", "2"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.MkdirAll(filepath.Join(s.Path(), "db0", "rp2", "4"), 0777); err != nil {
		t.Fatal(err)
	}

	if err := os.MkdirAll(filepath.Join(s.Path(), "db1", "rp0", "1"), 0777); err != nil {
		t.Fatal(err)
	}

	// Store should ignore shard since it does not have a numeric name.
	if err := s.Open(); err != nil {
		t.Fatal(err)
	} else if n := len(s.Databases()); n != 2 {
		t.Fatalf("unexpected database index count: %d", n)
	} else if n := s.ShardN(); n != 3 {
		t.Fatalf("unexpected shard count: %d", n)
	}

	expDatabases := []string{"db0", "db1"}
	gotDatabases := s.Databases()
	sort.Strings(gotDatabases)

	if got, exp := gotDatabases, expDatabases; !reflect.DeepEqual(got, exp) {
		t.Fatalf("got %#v, expected %#v", got, exp)
	}
}

// Ensure the store reports an error when it can't open a database directory.
func TestStore_Open_InvalidDatabaseFile(t *testing.T) {
	t.Parallel()

	s := NewStore()
	defer s.Close()

	// Create a file instead of a directory for a database.
	if _, err := os.Create(filepath.Join(s.Path(), "db0")); err != nil {
		t.Fatal(err)
	}

	// Store should ignore database since it's a file.
	if err := s.Open(); err != nil {
		t.Fatal(err)
	} else if n := len(s.Databases()); n != 0 {
		t.Fatalf("unexpected database index count: %d", n)
	}
}

// Ensure the store reports an error when it can't open a retention policy.
func TestStore_Open_InvalidRetentionPolicy(t *testing.T) {
	t.Parallel()

	s := NewStore()
	defer s.Close()

	// Create an RP file instead of a directory.
	if err := os.MkdirAll(filepath.Join(s.Path(), "db0"), 0777); err != nil {
		t.Fatal(err)
	} else if _, err := os.Create(filepath.Join(s.Path(), "db0", "rp0")); err != nil {
		t.Fatal(err)
	}

	// Store should ignore retention policy since it's a file, and there should
	// be no indices created.
	if err := s.Open(); err != nil {
		t.Fatal(err)
	} else if n := len(s.Databases()); n != 0 {
		t.Log(s.Databases())
		t.Fatalf("unexpected database index count: %d", n)
	}
}

// Ensure the store reports an error when it can't open a retention policy.
func TestStore_Open_InvalidShard(t *testing.T) {
	t.Parallel()

	s := NewStore()
	defer s.Close()

	// Create a non-numeric shard file.
	if err := os.MkdirAll(filepath.Join(s.Path(), "db0", "rp0"), 0777); err != nil {
		t.Fatal(err)
	} else if _, err := os.Create(filepath.Join(s.Path(), "db0", "rp0", "bad_shard")); err != nil {
		t.Fatal(err)
	}

	// Store should ignore shard since it does not have a numeric name.
	if err := s.Open(); err != nil {
		t.Fatal(err)
	} else if n := len(s.Databases()); n != 0 {
		t.Fatalf("unexpected database index count: %d", n)
	} else if n := s.ShardN(); n != 0 {
		t.Fatalf("unexpected shard count: %d", n)
	}
}

// Ensure shards can create iterators.
func TestShards_CreateIterator(t *testing.T) {
	t.Parallel()

	s := MustOpenStore()
	defer s.Close()

	// Create shard #0 with data.
	s.MustCreateShardWithData("db0", "rp0", 0,
		`cpu,host=serverA value=1  0`,
		`cpu,host=serverA value=2 10`,
		`cpu,host=serverB value=3 20`,
	)

	// Create shard #1 with data.
	s.MustCreateShardWithData("db0", "rp0", 1,
		`cpu,host=serverA value=1 30`,
		`mem,host=serverA value=2 40`, // skip: wrong source
		`cpu,host=serverC value=3 60`,
	)

	// Retrieve shard group.
	shards := s.ShardGroup([]uint64{0, 1})

	// Create iterator.
	itr, err := shards.CreateIterator("cpu", influxql.IteratorOptions{
		Expr:       influxql.MustParseExpr(`value`),
		Dimensions: []string{"host"},
		Ascending:  true,
		StartTime:  influxql.MinTime,
		EndTime:    influxql.MaxTime,
	})
	if err != nil {
		t.Fatal(err)
	}
	defer itr.Close()
	fitr := itr.(influxql.FloatIterator)

	// Read values from iterator. The host=serverA points should come first.
	if p, err := fitr.Next(); err != nil {
		t.Fatalf("unexpected error(0): %s", err)
	} else if !deep.Equal(p, &influxql.FloatPoint{Name: "cpu", Tags: ParseTags("host=serverA"), Time: time.Unix(0, 0).UnixNano(), Value: 1}) {
		t.Fatalf("unexpected point(0): %s", spew.Sdump(p))
	}
	if p, err := fitr.Next(); err != nil {
		t.Fatalf("unexpected error(1): %s", err)
	} else if !deep.Equal(p, &influxql.FloatPoint{Name: "cpu", Tags: ParseTags("host=serverA"), Time: time.Unix(10, 0).UnixNano(), Value: 2}) {
		t.Fatalf("unexpected point(1): %s", spew.Sdump(p))
	}
	if p, err := fitr.Next(); err != nil {
		t.Fatalf("unexpected error(2): %s", err)
	} else if !deep.Equal(p, &influxql.FloatPoint{Name: "cpu", Tags: ParseTags("host=serverA"), Time: time.Unix(30, 0).UnixNano(), Value: 1}) {
		t.Fatalf("unexpected point(2): %s", spew.Sdump(p))
	}

	// Next the host=serverB point.
	if p, err := fitr.Next(); err != nil {
		t.Fatalf("unexpected error(3): %s", err)
	} else if !deep.Equal(p, &influxql.FloatPoint{Name: "cpu", Tags: ParseTags("host=serverB"), Time: time.Unix(20, 0).UnixNano(), Value: 3}) {
		t.Fatalf("unexpected point(3): %s", spew.Sdump(p))
	}

	// And finally the host=serverC point.
	if p, err := fitr.Next(); err != nil {
		t.Fatalf("unexpected error(4): %s", err)
	} else if !deep.Equal(p, &influxql.FloatPoint{Name: "cpu", Tags: ParseTags("host=serverC"), Time: time.Unix(60, 0).UnixNano(), Value: 3}) {
		t.Fatalf("unexpected point(4): %s", spew.Sdump(p))
	}

	// Then an EOF should occur.
	if p, err := fitr.Next(); err != nil {
		t.Fatalf("expected eof, got error: %s", err)
	} else if p != nil {
		t.Fatalf("expected eof, got: %s", spew.Sdump(p))
	}
}

// Ensure the store can backup a shard and another store can restore it.
func TestStore_BackupRestoreShard(t *testing.T) {
	t.Parallel()

	s0, s1 := MustOpenStore(), MustOpenStore()
	defer s0.Close()
	defer s1.Close()

	// Create shard with data.
	s0.MustCreateShardWithData("db0", "rp0", 100,
		`cpu value=1 0`,
		`cpu value=2 10`,
		`cpu value=3 20`,
	)

	if err := s0.Reopen(); err != nil {
		t.Fatal(err)
	}

	// Backup shard to a buffer.
	var buf bytes.Buffer
	if err := s0.BackupShard(100, time.Time{}, &buf); err != nil {
		t.Fatal(err)
	}

	// Create the shard on the other store and restore from buffer.
	if err := s1.CreateShard("db0", "rp0", 100, true); err != nil {
		t.Fatal(err)
	}
	if err := s1.RestoreShard(100, &buf); err != nil {
		t.Fatal(err)
	}

	// Read data from
	itr, err := s0.Shard(100).CreateIterator("cpu", influxql.IteratorOptions{
		Expr:      influxql.MustParseExpr(`value`),
		Ascending: true,
		StartTime: influxql.MinTime,
		EndTime:   influxql.MaxTime,
	})
	if err != nil {
		t.Fatal(err)
	}
	fitr := itr.(influxql.FloatIterator)

	// Read values from iterator. The host=serverA points should come first.
	p, e := fitr.Next()
	if e != nil {
		t.Fatal(e)
	}
	if !deep.Equal(p, &influxql.FloatPoint{Name: "cpu", Time: time.Unix(0, 0).UnixNano(), Value: 1}) {
		t.Fatalf("unexpected point(0): %s", spew.Sdump(p))
	}
	p, e = fitr.Next()
	if e != nil {
		t.Fatal(e)
	}
	if !deep.Equal(p, &influxql.FloatPoint{Name: "cpu", Time: time.Unix(10, 0).UnixNano(), Value: 2}) {
		t.Fatalf("unexpected point(1): %s", spew.Sdump(p))
	}
	p, e = fitr.Next()
	if e != nil {
		t.Fatal(e)
	}
	if !deep.Equal(p, &influxql.FloatPoint{Name: "cpu", Time: time.Unix(20, 0).UnixNano(), Value: 3}) {
		t.Fatalf("unexpected point(2): %s", spew.Sdump(p))
	}
}

func TestStore_MeasurementNames_Deduplicate(t *testing.T) {
	t.Parallel()

	s := MustOpenStore()
	defer s.Close()

	// Create shard with data.
	s.MustCreateShardWithData("db0", "rp0", 1,
		`cpu value=1 0`,
		`cpu value=2 10`,
		`cpu value=3 20`,
	)

	// Create 2nd shard w/ same measurements.
	s.MustCreateShardWithData("db0", "rp0", 2,
		`cpu value=1 0`,
		`cpu value=2 10`,
		`cpu value=3 20`,
	)

	meas, err := s.MeasurementNames("db0", nil)
	if err != nil {
		t.Fatalf("unexpected error with MeasurementNames: %v", err)
	}

	if exp, got := 1, len(meas); exp != got {
		t.Fatalf("measurement len mismatch: exp %v, got %v", exp, got)
	}

	if exp, got := "cpu", string(meas[0]); exp != got {
		t.Fatalf("measurement name mismatch: exp %v, got %v", exp, got)
	}
}

func testStoreCardinalityTombstoning(t *testing.T, store *Store) {
	if testing.Short() || os.Getenv("GORACE") != "" || os.Getenv("APPVEYOR") != "" {
		t.Skip("Skipping test in short, race and appveyor mode.")
	}

	// Generate point data to write to the shards.
	series := genTestSeries(10, 2, 4) // 160 series

	points := make([]models.Point, 0, len(series))
	for _, s := range series {
		points = append(points, models.MustNewPoint(s.Measurement, s.Series.Tags(), map[string]interface{}{"value": 1.0}, time.Now()))
	}

	// Create requested number of shards in the store & write points across
	// shards such that we never write the same series to multiple shards.
	for shardID := 0; shardID < 4; shardID++ {
		if err := store.CreateShard("db", "rp", uint64(shardID), true); err != nil {
			t.Errorf("create shard: %s", err)
		}

		if err := store.BatchWrite(shardID, points[shardID*40:(shardID+1)*40]); err != nil {
			t.Errorf("batch write: %s", err)
		}
	}

	// Delete all the series for each measurement.
	mnames, err := store.MeasurementNames("db", nil)
	if err != nil {
		t.Fatal(err)
	}

	for _, name := range mnames {
		if err := store.DeleteSeries("db", []influxql.Source{&influxql.Measurement{Name: string(name)}}, nil); err != nil {
			t.Fatal(err)
		}
	}

	// Estimate the series cardinality...
	cardinality, err := store.Store.SeriesCardinality("db")
	if err != nil {
		t.Fatal(err)
	}

	// Estimated cardinality should be well within 10 of the actual cardinality.
	// TODO(edd): this epsilon is arbitrary. How can I make it better?
	if got, exp := cardinality, int64(10); got > exp {
		t.Errorf("series cardinality out by %v (expected within %v), estimation was: %d", got, exp, cardinality)
	}

	// Since all the series have been deleted, all the measurements should have
	// been removed from the index too.
	if cardinality, err = store.Store.MeasurementsCardinality("db"); err != nil {
		t.Fatal(err)
	}

	// Estimated cardinality should be well within 2 of the actual cardinality.
	// TODO(edd): this is totally arbitrary. How can I make it better?
	if got, exp := cardinality, int64(2); got > exp {
		t.Errorf("measurement cardinality out by %v (expected within %v), estimation was: %d", got, exp, cardinality)
	}
}

func TestStore_Cardinality_Tombstoning_Inmem(t *testing.T) {
	t.Parallel()

	store := NewStore()
	store.EngineOptions.Config.Index = "inmem"
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	testStoreCardinalityTombstoning(t, store)
}

func TestStore_Cardinality_Tombstoning_TSI(t *testing.T) {
	t.Parallel()

	store := NewStore()
	store.EngineOptions.Config.Index = "tsi1"
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	testStoreCardinalityTombstoning(t, store)
}

func testStoreCardinalityUnique(t *testing.T, store *Store) {
	if testing.Short() || os.Getenv("GORACE") != "" || os.Getenv("APPVEYOR") != "" {
		t.Skip("Skipping test in short, race and appveyor mode.")
	}

	// Generate point data to write to the shards.
	series := genTestSeries(64, 5, 5) // 200,000 series
	expCardinality := len(series)

	points := make([]models.Point, 0, len(series))
	for _, s := range series {
		points = append(points, models.MustNewPoint(s.Measurement, s.Series.Tags(), map[string]interface{}{"value": 1.0}, time.Now()))
	}

	// Create requested number of shards in the store & write points across
	// shards such that we never write the same series to multiple shards.
	for shardID := 0; shardID < 10; shardID++ {
		if err := store.CreateShard("db", "rp", uint64(shardID), true); err != nil {
			t.Fatalf("create shard: %s", err)
		}
		if err := store.BatchWrite(shardID, points[shardID*20000:(shardID+1)*20000]); err != nil {
			t.Fatalf("batch write: %s", err)
		}
	}

	// Estimate the series cardinality...
	cardinality, err := store.Store.SeriesCardinality("db")
	if err != nil {
		t.Fatal(err)
	}

	// Estimated cardinality should be well within 1.5% of the actual cardinality.
	if got, exp := math.Abs(float64(cardinality)-float64(expCardinality))/float64(expCardinality), 0.015; got > exp {
		t.Errorf("got epsilon of %v for series cardinality %v (expected %v), which is larger than expected %v", got, cardinality, expCardinality, exp)
	}

	// Estimate the measurement cardinality...
	if cardinality, err = store.Store.MeasurementsCardinality("db"); err != nil {
		t.Fatal(err)
	}

	// Estimated cardinality should be well within 2 of the actual cardinality. (arbitrary...)
	expCardinality = 64
	if got, exp := math.Abs(float64(cardinality)-float64(expCardinality)), 2.0; got > exp {
		t.Errorf("got measurmement cardinality %v, expected upto %v; difference is larger than expected %v", cardinality, expCardinality, exp)
	}
}

func TestStore_Cardinality_Unique_Inmem(t *testing.T) {
	t.Parallel()

	store := NewStore()
	store.EngineOptions.Config.Index = "inmem"
	store.EngineOptions.Config.MaxSeriesPerDatabase = 0
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	testStoreCardinalityUnique(t, store)
}

func TestStore_Cardinality_Unique_TSI1(t *testing.T) {
	t.Parallel()

	store := NewStore()
	store.EngineOptions.Config.Index = "tsi1"
	store.EngineOptions.Config.MaxSeriesPerDatabase = 0
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	testStoreCardinalityUnique(t, store)
}

// This test tests cardinality estimation when series data is duplicated across
// multiple shards.
func testStoreCardinalityDuplicates(t *testing.T, store *Store) {
	if testing.Short() || os.Getenv("GORACE") != "" || os.Getenv("APPVEYOR") != "" {
		t.Skip("Skipping test in short, race and appveyor mode.")
	}

	// Generate point data to write to the shards.
	series := genTestSeries(64, 5, 5) // 200,000 series.
	expCardinality := len(series)

	points := make([]models.Point, 0, len(series))
	for _, s := range series {
		points = append(points, models.MustNewPoint(s.Measurement, s.Series.Tags(), map[string]interface{}{"value": 1.0}, time.Now()))
	}

	// Create requested number of shards in the store & write points.
	for shardID := 0; shardID < 10; shardID++ {
		if err := store.CreateShard("db", "rp", uint64(shardID), true); err != nil {
			t.Fatalf("create shard: %s", err)
		}

		var from, to int
		if shardID == 0 {
			// if it's the first shard then write all of the points.
			from, to = 0, len(points)-1
		} else {
			// For other shards we write a random sub-section of all the points.
			// which will duplicate the series and shouldn't increase the
			// cardinality.
			from, to := rand.Intn(len(points)), rand.Intn(len(points))
			if from > to {
				from, to = to, from
			}
		}

		if err := store.BatchWrite(shardID, points[from:to]); err != nil {
			t.Fatalf("batch write: %s", err)
		}
	}

	// Estimate the series cardinality...
	cardinality, err := store.Store.SeriesCardinality("db")
	if err != nil {
		t.Fatal(err)
	}

	// Estimated cardinality should be well within 1.5% of the actual cardinality.
	if got, exp := math.Abs(float64(cardinality)-float64(expCardinality))/float64(expCardinality), 0.015; got > exp {
		t.Errorf("got epsilon of %v for series cardinality %d (expected %d), which is larger than expected %v", got, cardinality, expCardinality, exp)
	}

	// Estimate the measurement cardinality...
	if cardinality, err = store.Store.MeasurementsCardinality("db"); err != nil {
		t.Fatal(err)
	}

	// Estimated cardinality should be well within 2 of the actual cardinality. (Arbitrary...)
	expCardinality = 64
	if got, exp := math.Abs(float64(cardinality)-float64(expCardinality)), 2.0; got > exp {
		t.Errorf("got measurement cardinality %v, expected upto %v; difference is larger than expected %v", cardinality, expCardinality, exp)
	}
}

func TestStore_Cardinality_Duplicates_Inmem(t *testing.T) {
	t.Parallel()

	store := NewStore()
	store.EngineOptions.Config.Index = "inmem"
	store.EngineOptions.Config.MaxSeriesPerDatabase = 0
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	testStoreCardinalityDuplicates(t, store)
}

func TestStore_Cardinality_Duplicates_TSI1(t *testing.T) {
	t.Parallel()

	store := NewStore()
	store.EngineOptions.Config.Index = "tsi1"
	store.EngineOptions.Config.MaxSeriesPerDatabase = 0
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	testStoreCardinalityDuplicates(t, store)
}

// Creates a large number of series in multiple shards, which will force
// compactions to occur.
func testStoreCardinalityCompactions(t *testing.T, store *Store) {
	if testing.Short() || os.Getenv("GORACE") != "" || os.Getenv("APPVEYOR") != "" {
		t.Skip("Skipping test in short, race and appveyor mode.")
	}

	// Generate point data to write to the shards.
	series := genTestSeries(300, 5, 5) // 937,500 series
	expCardinality := len(series)

	points := make([]models.Point, 0, len(series))
	for _, s := range series {
		points = append(points, models.MustNewPoint(s.Measurement, s.Series.Tags(), map[string]interface{}{"value": 1.0}, time.Now()))
	}

	// Create requested number of shards in the store & write points across
	// shards such that we never write the same series to multiple shards.
	for shardID := 0; shardID < 2; shardID++ {
		if err := store.CreateShard("db", "rp", uint64(shardID), true); err != nil {
			t.Fatalf("create shard: %s", err)
		}
		if err := store.BatchWrite(shardID, points[shardID*468750:(shardID+1)*468750]); err != nil {
			t.Fatalf("batch write: %s", err)
		}
	}

	// Estimate the series cardinality...
	cardinality, err := store.Store.SeriesCardinality("db")
	if err != nil {
		t.Fatal(err)
	}

	// Estimated cardinality should be well within 1.5% of the actual cardinality.
	if got, exp := math.Abs(float64(cardinality)-float64(expCardinality))/float64(expCardinality), 0.015; got > exp {
		t.Errorf("got epsilon of %v for series cardinality %v (expected %v), which is larger than expected %v", got, cardinality, expCardinality, exp)
	}

	// Estimate the measurement cardinality...
	if cardinality, err = store.Store.MeasurementsCardinality("db"); err != nil {
		t.Fatal(err)
	}

	// Estimated cardinality should be well within 2 of the actual cardinality. (Arbitrary...)
	expCardinality = 300
	if got, exp := math.Abs(float64(cardinality)-float64(expCardinality)), 2.0; got > exp {
		t.Errorf("got measurement cardinality %v, expected upto %v; difference is larger than expected %v", cardinality, expCardinality, exp)
	}
}

func TestStore_Cardinality_Compactions_Inmem(t *testing.T) {
	t.Parallel()

	store := NewStore()
	store.EngineOptions.Config.Index = "inmem"
	store.EngineOptions.Config.MaxSeriesPerDatabase = 0
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	testStoreCardinalityCompactions(t, store)
}

func TestStore_Cardinality_Compactions_TSI1(t *testing.T) {
	t.Parallel()

	store := NewStore()
	store.EngineOptions.Config.Index = "tsi1"
	store.EngineOptions.Config.MaxSeriesPerDatabase = 0
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	testStoreCardinalityCompactions(t, store)
}

func TestStore_TagValues(t *testing.T) {
	t.Parallel()

	// No WHERE - just get for keys host and shard
	RHSAll := &influxql.ParenExpr{
		Expr: &influxql.BinaryExpr{
			Op: influxql.OR,
			LHS: &influxql.BinaryExpr{
				Op:  influxql.EQ,
				LHS: &influxql.VarRef{Val: "_tagKey"},
				RHS: &influxql.StringLiteral{Val: "host"},
			},
			RHS: &influxql.BinaryExpr{
				Op:  influxql.EQ,
				LHS: &influxql.VarRef{Val: "_tagKey"},
				RHS: &influxql.StringLiteral{Val: "shard"},
			},
		},
	}

	// Get for host and shard, but also WHERE on foo = a
	RHSWhere := &influxql.ParenExpr{
		Expr: &influxql.BinaryExpr{
			Op: influxql.AND,
			LHS: &influxql.ParenExpr{
				Expr: &influxql.BinaryExpr{
					Op:  influxql.EQ,
					LHS: &influxql.VarRef{Val: "foo"},
					RHS: &influxql.StringLiteral{Val: "a"},
				},
			},
			RHS: RHSAll,
		},
	}

	// SHOW TAG VALUES FROM /cpu\d/ WITH KEY IN ("host", "shard")
	//
	// Switching out RHS for RHSWhere would make the query:
	//    SHOW TAG VALUES FROM /cpu\d/ WITH KEY IN ("host", "shard") WHERE foo = 'a'
	base := influxql.BinaryExpr{
		Op: influxql.AND,
		LHS: &influxql.ParenExpr{
			Expr: &influxql.BinaryExpr{
				Op:  influxql.EQREGEX,
				LHS: &influxql.VarRef{Val: "_name"},
				RHS: &influxql.RegexLiteral{Val: regexp.MustCompile(`cpu\d`)},
			},
		},
		RHS: RHSAll,
	}

	var baseWhere *influxql.BinaryExpr = influxql.CloneExpr(&base).(*influxql.BinaryExpr)
	baseWhere.RHS = RHSWhere

	examples := []struct {
		Name string
		Expr influxql.Expr
		Exp  []tsdb.TagValues
	}{
		{
			Name: "No WHERE clause",
			Expr: &base,
			Exp: []tsdb.TagValues{
				createTagValues("cpu0", map[string][]string{"host": {"nofoo", "tv0", "tv1", "tv2", "tv3"}, "shard": {"s0", "s1", "s2"}}),
				createTagValues("cpu1", map[string][]string{"host": {"nofoo", "tv0", "tv1", "tv2", "tv3"}, "shard": {"s0", "s1", "s2"}}),
				createTagValues("cpu2", map[string][]string{"host": {"nofoo", "tv0", "tv1", "tv2", "tv3"}, "shard": {"s0", "s1", "s2"}}),
			},
		},
		{
			Name: "With WHERE clause",
			Expr: baseWhere,
			Exp: []tsdb.TagValues{
				createTagValues("cpu0", map[string][]string{"host": {"tv0", "tv1", "tv2", "tv3"}, "shard": {"s0", "s1", "s2"}}),
				createTagValues("cpu1", map[string][]string{"host": {"tv0", "tv1", "tv2", "tv3"}, "shard": {"s0", "s1", "s2"}}),
				createTagValues("cpu2", map[string][]string{"host": {"tv0", "tv1", "tv2", "tv3"}, "shard": {"s0", "s1", "s2"}}),
			},
		},
	}

	var s *Store
	setup := func(index string) {
		s = MustOpenStore()
		s.EngineOptions.IndexVersion = index

		fmtStr := `cpu%[1]d,foo=a,ignoreme=nope,host=tv%[2]d,shard=s%[3]d value=1 %[4]d
		cpu%[1]d,host=nofoo value=1 %[4]d
	mem,host=nothanks value=1 %[4]d
	`
		genPoints := func(sid int) []string {
			var ts int
			points := make([]string, 0, 3*4)
			for m := 0; m < 3; m++ {
				for tagvid := 0; tagvid < 4; tagvid++ {
					points = append(points, fmt.Sprintf(fmtStr, m, tagvid, sid, ts))
					ts++
				}
			}
			return points
		}

		// Create data across 3 shards.
		for i := 0; i < 3; i++ {
			s.MustCreateShardWithData("db0", "rp0", i, genPoints(i)...)
		}
	}

	indexes := []string{"inmem", "tsi1"}
	for _, example := range examples {
		for _, index := range indexes {
			setup(index)
			t.Run(example.Name+"_"+index, func(t *testing.T) {
				got, err := s.TagValues("db0", example.Expr)
				if err != nil {
					t.Fatal(err)
				}
				exp := example.Exp

				if !reflect.DeepEqual(got, exp) {
					t.Fatalf("got:\n%#v\n\nexp:\n%#v", got, exp)
				}
			})
			s.Close()
		}
	}
}

// Helper to create some tag values
func createTagValues(mname string, kvs map[string][]string) tsdb.TagValues {
	var sz int
	for _, v := range kvs {
		sz += len(v)
	}

	out := tsdb.TagValues{
		Measurement: mname,
		Values:      make([]tsdb.KeyValue, 0, sz),
	}

	for tk, tvs := range kvs {
		for _, tv := range tvs {
			out.Values = append(out.Values, tsdb.KeyValue{Key: tk, Value: tv})
		}
		// We have to sort the KeyValues since that's how they're provided from
		// the tsdb.Store.
		sort.Sort(tsdb.KeyValues(out.Values))
	}

	return out
}

func benchmarkStoreSeriesCardinality(b *testing.B, store *Store, n int) {
	// Write a point to n shards.
	for shardID := 0; shardID < n; shardID++ {
		if err := store.CreateShard("db", "rp", uint64(shardID), true); err != nil {
			b.Fatalf("create shard: %s", err)
		}

		err := store.WriteToShard(uint64(shardID), []models.Point{models.MustNewPoint("cpu", nil, map[string]interface{}{"value": 1.0}, time.Now())})
		if err != nil {
			b.Fatalf("write: %s", err)
		}
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		_, _ = store.SeriesCardinality("db")
	}
}

func BenchmarkStore_SeriesCardinality_100_Shards_Inmem(b *testing.B) {
	store := NewStore()
	store.EngineOptions.Config.Index = "inmem"
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	benchmarkStoreSeriesCardinality(b, store, 100)
}

func BenchmarkStore_SeriesCardinality_100_Shards_TSI(b *testing.B) {
	store := NewStore()
	store.EngineOptions.Config.Index = "tsi1"
	if err := store.Open(); err != nil {
		panic(err)
	}
	defer store.Close()
	benchmarkStoreSeriesCardinality(b, store, 100)
}

func BenchmarkStoreOpen_200KSeries_100Shards(b *testing.B) { benchmarkStoreOpen(b, 64, 5, 5, 1, 100) }

func benchmarkStoreOpen(b *testing.B, mCnt, tkCnt, tvCnt, pntCnt, shardCnt int) {
	var path string
	if err := func() error {
		store := MustOpenStore()
		defer store.Store.Close()
		path = store.Path()

		// Generate test series (measurements + unique tag sets).
		series := genTestSeries(mCnt, tkCnt, tvCnt)

		// Generate point data to write to the shards.
		points := []models.Point{}
		for _, s := range series {
			for val := 0.0; val < float64(pntCnt); val++ {
				p := models.MustNewPoint(s.Measurement, s.Series.Tags(), map[string]interface{}{"value": val}, time.Now())
				points = append(points, p)
			}
		}

		// Create requested number of shards in the store & write points.
		for shardID := 0; shardID < shardCnt; shardID++ {
			if err := store.CreateShard("mydb", "myrp", uint64(shardID), true); err != nil {
				return fmt.Errorf("create shard: %s", err)
			}
			if err := store.BatchWrite(shardID, points); err != nil {
				return fmt.Errorf("batch write: %s", err)
			}
		}
		return nil
	}(); err != nil {
		b.Fatal(err)
	}
	defer os.RemoveAll(path)

	// Run the benchmark loop.
	b.ResetTimer()
	for n := 0; n < b.N; n++ {
		store := tsdb.NewStore(path)
		if err := store.Open(); err != nil {
			b.Fatalf("open store error: %s", err)
		}

		b.StopTimer()
		store.Close()
		b.StartTimer()
	}
}

// To store result of benchmark (ensure allocated on heap).
var tvResult []tsdb.TagValues

func BenchmarkStore_TagValues(b *testing.B) {
	benchmarks := []struct {
		name         string
		shards       int
		measurements int
		tagValues    int
	}{
		{name: "s=1_m=1_v=100", shards: 1, measurements: 1, tagValues: 100},
		{name: "s=1_m=1_v=1000", shards: 1, measurements: 1, tagValues: 1000},
		{name: "s=1_m=10_v=100", shards: 1, measurements: 10, tagValues: 100},
		{name: "s=1_m=10_v=1000", shards: 1, measurements: 10, tagValues: 1000},
		{name: "s=1_m=100_v=100", shards: 1, measurements: 100, tagValues: 100},
		{name: "s=1_m=100_v=1000", shards: 1, measurements: 100, tagValues: 1000},
		{name: "s=10_m=1_v=100", shards: 10, measurements: 1, tagValues: 100},
		{name: "s=10_m=1_v=1000", shards: 10, measurements: 1, tagValues: 1000},
		{name: "s=10_m=10_v=100", shards: 10, measurements: 10, tagValues: 100},
		{name: "s=10_m=10_v=1000", shards: 10, measurements: 10, tagValues: 1000},
		{name: "s=10_m=100_v=100", shards: 10, measurements: 100, tagValues: 100},
		{name: "s=10_m=100_v=1000", shards: 10, measurements: 100, tagValues: 1000},
	}

	var s *Store
	setup := func(shards, measurements, tagValues int, index string, useRandom bool) {
		s = NewStore()
		s.EngineOptions.IndexVersion = index
		if err := s.Open(); err != nil {
			panic(err)
		}

		fmtStr := `cpu%[1]d,host=tv%[2]d,shard=s%[3]d,z1=s%[1]d%[2]d,z2=%[4]s value=1 %[5]d`
		// genPoints generates some point data. If ran is true then random tag
		// key values will be generated, meaning more work sorting and merging.
		// If ran is false, then the same set of points will be produced for the
		// same set of parameters, meaning more de-duplication of points will be
		// needed.
		genPoints := func(sid int, ran bool) []string {
			var v, ts int
			var half string
			points := make([]string, 0, measurements*tagValues)
			for m := 0; m < measurements; m++ {
				for tagvid := 0; tagvid < tagValues; tagvid++ {
					v = tagvid
					if ran {
						v = rand.Intn(100000)
					}
					half = fmt.Sprint(rand.Intn(2) == 0)
					points = append(points, fmt.Sprintf(fmtStr, m, v, sid, half, ts))
					ts++
				}
			}
			return points
		}

		// Create data across chosen number of shards.
		for i := 0; i < shards; i++ {
			s.MustCreateShardWithData("db0", "rp0", i, genPoints(i, useRandom)...)
		}
	}

	teardown := func() {
		if err := s.Close(); err != nil {
			b.Fatal(err)
		}
	}

	// SHOW TAG VALUES WITH KEY IN ("host", "shard")
	cond1 := &influxql.ParenExpr{
		Expr: &influxql.BinaryExpr{
			Op: influxql.OR,
			LHS: &influxql.BinaryExpr{
				Op:  influxql.EQ,
				LHS: &influxql.VarRef{Val: "_tagKey"},
				RHS: &influxql.StringLiteral{Val: "host"},
			},
			RHS: &influxql.BinaryExpr{
				Op:  influxql.EQ,
				LHS: &influxql.VarRef{Val: "_tagKey"},
				RHS: &influxql.StringLiteral{Val: "shard"},
			},
		},
	}

	cond2 := &influxql.ParenExpr{
		Expr: &influxql.BinaryExpr{
			Op: influxql.AND,
			LHS: &influxql.ParenExpr{
				Expr: &influxql.BinaryExpr{
					Op:  influxql.EQ,
					LHS: &influxql.VarRef{Val: "z2"},
					RHS: &influxql.StringLiteral{Val: "true"},
				},
			},
			RHS: cond1,
		},
	}

	var err error
	for _, index := range []string{"inmem", "tsi1"} {
		for useRand := 0; useRand < 2; useRand++ {
			for c, condition := range []influxql.Expr{cond1, cond2} {
				for _, bm := range benchmarks {
					setup(bm.shards, bm.measurements, bm.tagValues, index, useRand == 1)
					cnd := "Unfiltered"
					if c == 0 {
						cnd = "Filtered"
					}
					b.Run("random_values="+fmt.Sprint(useRand == 1)+"_index="+index+"_"+cnd+"_"+bm.name, func(b *testing.B) {
						for i := 0; i < b.N; i++ {
							if tvResult, err = s.TagValues("db0", condition); err != nil {
								b.Fatal(err)
							}
						}
					})
					teardown()
				}
			}
		}
	}
}

// Store is a test wrapper for tsdb.Store.
type Store struct {
	*tsdb.Store
}

// NewStore returns a new instance of Store with a temporary path.
func NewStore() *Store {
	path, err := ioutil.TempDir("", "influxdb-tsdb-")
	if err != nil {
		panic(err)
	}

	s := &Store{Store: tsdb.NewStore(path)}
	s.EngineOptions.Config.WALDir = filepath.Join(path, "wal")
	s.EngineOptions.Config.TraceLoggingEnabled = true

	if testing.Verbose() {
		s.WithLogger(zap.New(
			zap.NewTextEncoder(),
			zap.Output(os.Stdout),
		))
	}
	return s
}

// MustOpenStore returns a new, open Store using the default index,
// at a temporary path.
func MustOpenStore() *Store {
	s := NewStore()
	if err := s.Open(); err != nil {
		panic(err)
	}
	return s
}

// Reopen closes and reopens the store as a new store.
func (s *Store) Reopen() error {
	if err := s.Store.Close(); err != nil {
		return err
	}
	s.Store = tsdb.NewStore(s.Path())
	s.EngineOptions.Config.WALDir = filepath.Join(s.Path(), "wal")
	return s.Open()
}

// Close closes the store and removes the underlying data.
func (s *Store) Close() error {
	defer os.RemoveAll(s.Path())
	return s.Store.Close()
}

// MustCreateShardWithData creates a shard and writes line protocol data to it.
func (s *Store) MustCreateShardWithData(db, rp string, shardID int, data ...string) {
	if err := s.CreateShard(db, rp, uint64(shardID), true); err != nil {
		panic(err)
	}
	s.MustWriteToShardString(shardID, data...)
}

// MustWriteToShardString parses the line protocol (with second precision) and
// inserts the resulting points into a shard. Panic on error.
func (s *Store) MustWriteToShardString(shardID int, data ...string) {
	var points []models.Point
	for i := range data {
		a, err := models.ParsePointsWithPrecision([]byte(strings.TrimSpace(data[i])), time.Time{}, "s")
		if err != nil {
			panic(err)
		}
		points = append(points, a...)
	}

	if err := s.WriteToShard(uint64(shardID), points); err != nil {
		panic(err)
	}
}

// BatchWrite writes points to a shard in chunks.
func (s *Store) BatchWrite(shardID int, points []models.Point) error {
	nPts := len(points)
	chunkSz := 10000
	start := 0
	end := chunkSz

	for {
		if end > nPts {
			end = nPts
		}
		if end-start == 0 {
			break
		}

		if err := s.WriteToShard(uint64(shardID), points[start:end]); err != nil {
			return err
		}
		start = end
		end += chunkSz
	}
	return nil
}

// ParseTags returns an instance of Tags for a comma-delimited list of key/values.
func ParseTags(s string) influxql.Tags {
	m := make(map[string]string)
	for _, kv := range strings.Split(s, ",") {
		a := strings.Split(kv, "=")
		m[a[0]] = a[1]
	}
	return influxql.NewTags(m)
}

func dirExists(path string) bool {
	var err error
	if _, err = os.Stat(path); err == nil {
		return true
	}
	return !os.IsNotExist(err)
}
