mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-08-28 02:44:59 +00:00
* Removed support for EPOCH column constraint from TIMESTAMP SQL data type. * Implicit conversion of integers to timestamp will treat the integer value as seconds since unix epoch. * Add new ToTimeStamp(num, timeunit) SQL scalar function to help convert integer values to timestamp.
403 lines
9.2 KiB
Go
403 lines
9.2 KiB
Go
package dax_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/featurebasedb/featurebase/v3/dax"
|
|
"github.com/featurebasedb/featurebase/v3/pql"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func TestTable(t *testing.T) {
|
|
tableName := dax.TableName("foo")
|
|
|
|
t.Run("StringKeys", func(t *testing.T) {
|
|
// No PrimaryKeys.
|
|
{
|
|
tbl := &dax.Table{
|
|
Name: tableName,
|
|
}
|
|
assert.False(t, tbl.StringKeys())
|
|
}
|
|
// One PrimaryKey (string).
|
|
{
|
|
tbl := &dax.Table{
|
|
Name: tableName,
|
|
Fields: []*dax.Field{
|
|
{
|
|
Name: dax.PrimaryKeyFieldName,
|
|
Type: dax.BaseTypeString,
|
|
},
|
|
{
|
|
Name: "stringField2",
|
|
Type: dax.BaseTypeString,
|
|
},
|
|
},
|
|
}
|
|
assert.True(t, tbl.StringKeys())
|
|
}
|
|
// One PrimaryKey (non-string).
|
|
{
|
|
tbl := &dax.Table{
|
|
Name: tableName,
|
|
Fields: []*dax.Field{
|
|
{
|
|
Name: dax.PrimaryKeyFieldName,
|
|
Type: dax.BaseTypeID,
|
|
},
|
|
},
|
|
}
|
|
assert.False(t, tbl.StringKeys())
|
|
}
|
|
})
|
|
|
|
t.Run("Tables", func(t *testing.T) {
|
|
tblA := &dax.Table{
|
|
Name: "a",
|
|
}
|
|
tblZ := &dax.Table{
|
|
Name: "z",
|
|
}
|
|
|
|
tables := []*dax.Table{tblZ, tblA}
|
|
|
|
// Ensure tables starts out of order.
|
|
assert.Equal(t, tblZ.Name, tables[0].Name)
|
|
assert.Equal(t, tblA.Name, tables[1].Name)
|
|
|
|
sort.Sort(dax.Tables(tables))
|
|
|
|
// Ensure tables is ordered by name.
|
|
assert.Equal(t, tblA.Name, tables[0].Name)
|
|
assert.Equal(t, tblZ.Name, tables[1].Name)
|
|
|
|
})
|
|
|
|
t.Run("CreateSQL", func(t *testing.T) {
|
|
tests := []struct {
|
|
tbl dax.Table
|
|
expSQL string
|
|
}{
|
|
{
|
|
tbl: dax.Table{
|
|
Name: "just_a_table",
|
|
Fields: []*dax.Field{
|
|
{
|
|
Name: "_id",
|
|
Type: "id",
|
|
},
|
|
},
|
|
},
|
|
expSQL: "CREATE TABLE just_a_table (_id id) KEYPARTITIONS 0",
|
|
},
|
|
{
|
|
tbl: dax.Table{
|
|
Name: "all_field_types",
|
|
Fields: []*dax.Field{
|
|
{
|
|
Name: "_id",
|
|
Type: "string",
|
|
},
|
|
{
|
|
Name: "an_id",
|
|
Type: "id",
|
|
},
|
|
{
|
|
Name: "a_string",
|
|
Type: "string",
|
|
},
|
|
{
|
|
Name: "an_id_set",
|
|
Type: "idset",
|
|
},
|
|
{
|
|
Name: "a_string_set",
|
|
Type: "stringset",
|
|
},
|
|
{
|
|
Name: "an_int",
|
|
Type: "int",
|
|
},
|
|
{
|
|
Name: "a_decimal",
|
|
Type: "decimal",
|
|
},
|
|
{
|
|
Name: "a_timestamp",
|
|
Type: "timestamp",
|
|
},
|
|
},
|
|
},
|
|
expSQL: "CREATE TABLE all_field_types (_id string, an_id id, a_string string, an_id_set idset, a_string_set stringset, an_int int MIN 0 MAX 0, a_decimal decimal, a_timestamp timestamp) KEYPARTITIONS 0",
|
|
},
|
|
{
|
|
tbl: dax.Table{
|
|
Name: "all_field_types_with_options",
|
|
Fields: []*dax.Field{
|
|
{
|
|
Name: "_id",
|
|
Type: "string",
|
|
},
|
|
{
|
|
Name: "an_id",
|
|
Type: "id",
|
|
Options: dax.FieldOptions{
|
|
CacheType: "ranked",
|
|
CacheSize: 500,
|
|
},
|
|
},
|
|
{
|
|
Name: "a_string",
|
|
Type: "string",
|
|
Options: dax.FieldOptions{
|
|
CacheType: "ranked",
|
|
CacheSize: 500,
|
|
},
|
|
},
|
|
{
|
|
Name: "an_id_set",
|
|
Type: "idset",
|
|
Options: dax.FieldOptions{
|
|
CacheType: "ranked",
|
|
CacheSize: 500,
|
|
},
|
|
},
|
|
{
|
|
Name: "a_string_set",
|
|
Type: "stringset",
|
|
Options: dax.FieldOptions{
|
|
CacheType: "ranked",
|
|
CacheSize: 500,
|
|
},
|
|
},
|
|
{
|
|
Name: "an_int",
|
|
Type: "int",
|
|
Options: dax.FieldOptions{
|
|
Min: pql.NewDecimal(-100, 0),
|
|
Max: pql.NewDecimal(200, 0),
|
|
},
|
|
},
|
|
{
|
|
Name: "a_decimal",
|
|
Type: "decimal",
|
|
Options: dax.FieldOptions{},
|
|
},
|
|
{
|
|
Name: "a_timestamp",
|
|
Type: "timestamp",
|
|
Options: dax.FieldOptions{
|
|
TimeUnit: "s",
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expSQL: "CREATE TABLE all_field_types_with_options (_id string, an_id id CACHETYPE ranked SIZE 500, a_string string CACHETYPE ranked SIZE 500, an_id_set idset CACHETYPE ranked SIZE 500, a_string_set stringset CACHETYPE ranked SIZE 500, an_int int MIN -100 MAX 200, a_decimal decimal, a_timestamp timestamp TIMEUNIT 's') KEYPARTITIONS 0",
|
|
},
|
|
}
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
|
|
assert.Equal(t, test.expSQL, test.tbl.CreateSQL())
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("Table", func(t *testing.T) {
|
|
// CleanStub makes sure that the portion of the dax.TableName that we
|
|
// use in the pilosa.Index.Name is actually valid.
|
|
t.Run("CleanStub", func(t *testing.T) {
|
|
tests := []struct {
|
|
name dax.TableName
|
|
expStub string
|
|
}{
|
|
{
|
|
name: "",
|
|
expStub: "",
|
|
},
|
|
{
|
|
name: "foo",
|
|
expStub: "foo",
|
|
},
|
|
{
|
|
name: "AbCdEfG1234",
|
|
expStub: "abcdefg123",
|
|
},
|
|
{
|
|
name: "foo_bar_",
|
|
expStub: "foobar",
|
|
},
|
|
{
|
|
name: "!!!!!!!",
|
|
expStub: "",
|
|
},
|
|
{
|
|
name: "long1234567890",
|
|
expStub: "long123456",
|
|
},
|
|
{
|
|
name: "&&&&&&&&&&&&&&valid_stuff",
|
|
expStub: "validstuff",
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
t.Run(fmt.Sprintf("test-%d", i), func(t *testing.T) {
|
|
n := dax.NewTable(test.name)
|
|
assert.Empty(t, n.ID)
|
|
n.CreateID()
|
|
|
|
parts := strings.Split(string(n.ID), "_")
|
|
assert.Equal(t, test.expStub, parts[0])
|
|
})
|
|
}
|
|
})
|
|
|
|
t.Run("RandomID", func(t *testing.T) {
|
|
n := dax.NewTable(tableName)
|
|
assert.Empty(t, n.ID)
|
|
n.CreateID()
|
|
assert.NotEmpty(t, n.ID)
|
|
assert.Equal(t, tableName, n.Name)
|
|
})
|
|
|
|
t.Run("ToJSON", func(t *testing.T) {
|
|
t.Run("WithID", func(t *testing.T) {
|
|
n := dax.NewTable(tableName)
|
|
n.CreateID()
|
|
id := n.ID
|
|
assert.NotEmpty(t, id)
|
|
|
|
b, err := json.Marshal(n)
|
|
assert.NoError(t, err)
|
|
|
|
exp := fmt.Sprintf("{\"id\":\"%s\",\"name\":\"%s\",\"fields\":[],\"partitionN\":0}", id, tableName)
|
|
assert.JSONEq(t, exp, string(b))
|
|
})
|
|
|
|
t.Run("WithoutID", func(t *testing.T) {
|
|
n := dax.Table{
|
|
Name: tableName,
|
|
}
|
|
id := n.ID
|
|
assert.Empty(t, id)
|
|
|
|
b, err := json.Marshal(n)
|
|
assert.NoError(t, err)
|
|
|
|
exp := fmt.Sprintf("{\"name\":\"%s\",\"fields\":null,\"partitionN\":0}", tableName)
|
|
assert.JSONEq(t, exp, string(b))
|
|
})
|
|
})
|
|
|
|
t.Run("FromJSON", func(t *testing.T) {
|
|
tid := dax.TableID("0000000000abc123") // 11256099
|
|
j := fmt.Sprintf("{\"id\":\"%s\",\"name\":\"foo\",\"fields\":[],\"partitionN\":0}", tid)
|
|
|
|
tbl := &dax.Table{}
|
|
err := json.Unmarshal([]byte(j), tbl)
|
|
assert.NoError(t, err)
|
|
assert.Equal(t, tid, tbl.ID)
|
|
})
|
|
})
|
|
|
|
t.Run("QualifiedTable", func(t *testing.T) {
|
|
orgID := dax.OrganizationID("acme")
|
|
dbID := dax.DatabaseID("db1")
|
|
|
|
t.Run("New", func(t *testing.T) {
|
|
tbl := dax.NewTable(tableName)
|
|
tbl.CreateID()
|
|
qdbid := dax.QualifiedDatabaseID{
|
|
OrganizationID: orgID,
|
|
DatabaseID: dbID,
|
|
}
|
|
qtbl := dax.NewQualifiedTable(qdbid, tbl)
|
|
assert.NotEmpty(t, qtbl.ID)
|
|
assert.Equal(t, tbl.ID, qtbl.ID)
|
|
|
|
tq := qtbl.Qualifier()
|
|
assert.Equal(t, qdbid.OrganizationID, tq.OrganizationID)
|
|
assert.Equal(t, qdbid.DatabaseID, tq.DatabaseID)
|
|
|
|
wrappedTable := qtbl.Table
|
|
assert.Equal(t, tbl.Name, wrappedTable.Name)
|
|
assert.Equal(t, tbl.ID, wrappedTable.ID)
|
|
|
|
// Key.
|
|
exp := dax.TableKey(fmt.Sprintf("%s%s%s%s%s%s%s",
|
|
dax.PrefixTable,
|
|
dax.TableKeyDelimiter,
|
|
orgID,
|
|
dax.TableKeyDelimiter,
|
|
dbID,
|
|
dax.TableKeyDelimiter,
|
|
tbl.ID,
|
|
))
|
|
assert.Equal(t, exp, qtbl.Key())
|
|
})
|
|
|
|
t.Run("ToJSON", func(t *testing.T) {
|
|
tbl := dax.NewTable(tableName)
|
|
tbl.CreateID()
|
|
qdbid := dax.QualifiedDatabaseID{
|
|
OrganizationID: orgID,
|
|
DatabaseID: dbID,
|
|
}
|
|
qtbl := dax.NewQualifiedTable(qdbid, tbl)
|
|
id := qtbl.ID
|
|
|
|
b, err := json.Marshal(qtbl)
|
|
assert.NoError(t, err)
|
|
|
|
exp := fmt.Sprintf("{\"org-id\":\"%s\",\"db-id\":\"%s\",\"id\":\"%s\",\"name\":\"%s\",\"fields\":[],\"partitionN\":0}", orgID, dbID, id, tableName)
|
|
assert.JSONEq(t, exp, string(b))
|
|
})
|
|
|
|
t.Run("FromJSON", func(t *testing.T) {
|
|
tid := dax.TableID("0000000000abc123") // 11256099
|
|
j := fmt.Sprintf("{\"org-id\":\"%s\",\"db-id\":\"%s\",\"id\":\"%s\",\"name\":\"foo\",\"fields\":[],\"partitionN\":0}", orgID, dbID, tid)
|
|
|
|
qtbl := &dax.QualifiedTable{}
|
|
err := json.Unmarshal([]byte(j), qtbl)
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, tid, qtbl.ID)
|
|
assert.Equal(t, orgID, qtbl.Qualifier().OrganizationID)
|
|
assert.Equal(t, dbID, qtbl.Qualifier().DatabaseID)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestDatabase(t *testing.T) {
|
|
databaseName := dax.DatabaseName("db1")
|
|
|
|
t.Run("Options", func(t *testing.T) {
|
|
{
|
|
db := &dax.Database{
|
|
Name: databaseName,
|
|
}
|
|
assert.Zero(t, db.Options.WorkersMin)
|
|
assert.Zero(t, db.Options.WorkersMax)
|
|
|
|
// Set WorkersMin to 5.
|
|
assert.NoError(t, db.Options.Set(dax.DatabaseOptionWorkersMin, "5"))
|
|
assert.Equal(t, 5, db.Options.WorkersMin)
|
|
assert.Equal(t, 5, db.Options.WorkersMax)
|
|
|
|
// Set WorkersMin back to 0.
|
|
assert.NoError(t, db.Options.Set(dax.DatabaseOptionWorkersMin, "0"))
|
|
assert.Zero(t, db.Options.WorkersMin)
|
|
assert.Zero(t, db.Options.WorkersMax)
|
|
|
|
// Try setting WorkersMin to an invalid value.
|
|
assert.Error(t, db.Options.Set(dax.DatabaseOptionWorkersMin, "abc"))
|
|
|
|
// Try setting an unsupported option.
|
|
assert.Error(t, db.Options.Set("invalid-option", ""))
|
|
}
|
|
})
|
|
}
|