mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-06 16:45:55 +00:00
* initial changes to add bool support in idk * modifying some default parameters for testing, will revert them later * adding support for bool in making fragments function * boolean values implementation without supporting empty or null values at this point * Implement bool support in batch using a map (and a slice for nulls) (#2247) * Implement bool support in batch using a map (and a slice for nulls) * Keep the PackBools default for now But set it explicity in the ingest tests which rely on it. * Modify batch to construct bool update like mutex The code in API.ImportRoaringShard has a switch statement which causes bool fields to be handled like mutex fields. This means, that the viewUpdate.Clear value should only contain data in the first "row" of the fragment, which it will treat as records to clear for *all* rows. This makes more sense for mutex fields; for bool fields, there's only one other row to clear. But since the code is currently handling them the same, we need to construct viewUpdate.Clear such that it conforms to that pattern. This commit also adds a test which covers this logic. * Remove commented code; revert config for testing This commit also removes the DELETE_SENTINEL case for non-packed bools, since that isn't supported anyway. * Revert default setting * remove inconsistent type scope * correcting the logic of string converstion to bool * resolving an error in a test * adding tests to cover code related to bool support in batch.go file and interface.go files * modifying interfaces test * added one more test case Co-authored-by: Travis Turner <travis@pilosa.com> Co-authored-by: Travis Turner <travis@molecula.com>
460 lines
9.5 KiB
Go
460 lines
9.5 KiB
Go
package idk
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestDecimalFieldPilosafy(t *testing.T) {
|
|
f := DecimalField{NameVal: "a", Scale: 3}
|
|
if ret, err := f.PilosafyVal(float32(0.9)); err != nil || ret != int64(900) {
|
|
t.Errorf("got: %v of %[1]T, err: %v", ret, err)
|
|
}
|
|
|
|
}
|
|
|
|
func TestBoolFieldPilosafy(t *testing.T) {
|
|
f := BoolField{NameVal: "boolcol"}
|
|
validTrue := []interface{}{true, 1, "t", "true", " T ", " True"}
|
|
validFalse := []interface{}{false, 0, "f", "false", " F ", " False"}
|
|
invalid := []interface{}{"boat", " test "}
|
|
|
|
for i, v := range validTrue {
|
|
if ret, err := f.PilosafyVal(v); err != nil || ret != true {
|
|
t.Errorf("test: %d, got: %v of %[1]T, err: %v", i, ret, err)
|
|
}
|
|
}
|
|
for i, v := range validFalse {
|
|
if ret, err := f.PilosafyVal(v); err != nil || ret != false {
|
|
t.Errorf("test: %d, got: %v of %[1]T, err: %v", i, ret, err)
|
|
}
|
|
}
|
|
for i, v := range invalid {
|
|
if ret, err := f.PilosafyVal(v); err == nil {
|
|
t.Errorf("test: %d, got: %v of %[1]T, err: %v", i, ret, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPilosafyVal(t *testing.T) {
|
|
tests := []struct {
|
|
field Field
|
|
vals []interface{}
|
|
exps []interface{}
|
|
expErrs []string
|
|
}{
|
|
{
|
|
field: StringArrayField{},
|
|
vals: []interface{}{
|
|
"a,b,c",
|
|
"[a,b,c]",
|
|
""},
|
|
exps: []interface{}{
|
|
[]string{"a", "b", "c"},
|
|
[]string{"a", "b", "c"},
|
|
[]string(nil)},
|
|
},
|
|
{
|
|
field: IDArrayField{},
|
|
vals: []interface{}{
|
|
"1,2,3",
|
|
"[1,2,3]",
|
|
""},
|
|
exps: []interface{}{
|
|
[]uint64{1, 2, 3},
|
|
[]uint64{1, 2, 3},
|
|
[]uint64(nil)},
|
|
},
|
|
{
|
|
field: IntField{},
|
|
vals: []interface{}{
|
|
1,
|
|
"1",
|
|
"",
|
|
nil,
|
|
},
|
|
exps: []interface{}{
|
|
int64(1),
|
|
int64(1),
|
|
nil,
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
field: DecimalField{Scale: 2},
|
|
vals: []interface{}{
|
|
1,
|
|
"1",
|
|
"",
|
|
nil,
|
|
},
|
|
exps: []interface{}{
|
|
int64(100),
|
|
int64(100),
|
|
nil,
|
|
nil,
|
|
},
|
|
},
|
|
{
|
|
field: IDField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{nil},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: BoolField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{nil},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: StringField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{""},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: IntField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{nil},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: DecimalField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{nil},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: StringArrayField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{[]string(nil)},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: IDArrayField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{[]uint64(nil)},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: DateIntField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{nil},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: RecordTimeField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{nil},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: TimestampField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{nil},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: SignedIntBoolKeyField{},
|
|
vals: []interface{}{"", int64(-2), int(2), uint64(3)},
|
|
exps: []interface{}{nil, int64(-2), int64(2), int64(3)},
|
|
expErrs: []string(nil),
|
|
},
|
|
{
|
|
field: IgnoreField{},
|
|
vals: []interface{}{""},
|
|
exps: []interface{}{nil},
|
|
expErrs: []string(nil),
|
|
},
|
|
}
|
|
|
|
for i, test := range tests {
|
|
if len(test.exps) > 0 && len(test.vals) != len(test.exps) {
|
|
panic(fmt.Sprintf("invalid test at %d", i))
|
|
}
|
|
if len(test.expErrs) > 0 && len(test.vals) != len(test.expErrs) {
|
|
panic(fmt.Sprintf("invalid test at %d", i))
|
|
}
|
|
for j, val := range test.vals {
|
|
t.Run(fmt.Sprintf("test%dval%d", i, j), func(t *testing.T) {
|
|
pval, err := test.field.PilosafyVal(val)
|
|
if len(test.exps) > 0 {
|
|
errorIfNotEqual(t, test.exps[j], pval)
|
|
}
|
|
if len(test.expErrs) > 0 {
|
|
errorIfNotErrMatch(t, test.expErrs[j], err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTTLOf(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
field Field
|
|
expectedTTL time.Duration
|
|
expectedErr string
|
|
}{
|
|
{
|
|
name: "ttl StringField",
|
|
field: StringField{NameVal: "a", Mutex: false, Quantum: "YMD", TTL: "1s"},
|
|
expectedTTL: time.Second,
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
name: "ttl IDField",
|
|
field: IDField{NameVal: "a", Mutex: false, Quantum: "YMD", TTL: "60s"},
|
|
expectedTTL: time.Second * 60,
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
name: "ttl StringArrayField",
|
|
field: StringArrayField{NameVal: "a", Quantum: "YMD", TTL: "1h"},
|
|
expectedTTL: time.Hour,
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
name: "ttl IDArrayField",
|
|
field: StringArrayField{NameVal: "a", Quantum: "YMD", TTL: "1h"},
|
|
expectedTTL: time.Hour,
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
name: "ttl bad format",
|
|
field: StringField{NameVal: "a", Mutex: false, Quantum: "YMD", TTL: "0ssss"},
|
|
expectedTTL: 0,
|
|
expectedErr: "unable to parse TTL",
|
|
},
|
|
{
|
|
name: "ttl empty",
|
|
field: StringField{NameVal: "a", Mutex: false, Quantum: "YMD", TTL: ""},
|
|
expectedTTL: 0,
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
name: "no ttl",
|
|
field: StringField{NameVal: "a", Mutex: false, Quantum: "YMD"},
|
|
expectedTTL: 0,
|
|
expectedErr: "",
|
|
},
|
|
{
|
|
name: "ttl IntField",
|
|
field: IntField{NameVal: "a"},
|
|
expectedTTL: 0,
|
|
expectedErr: "",
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
ttl, err := TTLOf(test.field)
|
|
if ttl != test.expectedTTL {
|
|
t.Errorf("expected TTL: '%v', got: '%v'", test.expectedTTL, ttl)
|
|
}
|
|
if err != nil && !strings.Contains(err.Error(), test.expectedErr) {
|
|
t.Errorf("expected error: '%s', got: '%s'", test.expectedErr, err.Error())
|
|
}
|
|
})
|
|
}
|
|
|
|
}
|
|
|
|
func TestTimestampFieldValidateDuration(t *testing.T) {
|
|
errTimeOutsideEpoch := "value + epoch is too far from Unix epoch"
|
|
type args struct {
|
|
dur int64
|
|
offset int64
|
|
unit Unit
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
errStr string
|
|
}{
|
|
{
|
|
name: "max-max",
|
|
args: args{
|
|
dur: 0,
|
|
offset: TimestampToVal(Unit("s"), MaxTimestamp),
|
|
unit: Unit("s"),
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "oor-max",
|
|
args: args{
|
|
dur: 1,
|
|
offset: TimestampToVal(Unit("s"), MaxTimestamp),
|
|
unit: Unit("s"),
|
|
},
|
|
wantErr: true,
|
|
errStr: errTimeOutsideEpoch,
|
|
},
|
|
{
|
|
name: "max-oor",
|
|
args: args{
|
|
dur: 0,
|
|
offset: TimestampToVal(Unit("s"), MaxTimestamp.Add(1*time.Second)),
|
|
unit: Unit("s"),
|
|
},
|
|
wantErr: true,
|
|
errStr: errTimeOutsideEpoch,
|
|
},
|
|
{
|
|
name: "min-min",
|
|
args: args{
|
|
dur: 0,
|
|
offset: TimestampToVal(Unit("s"), MinTimestamp),
|
|
unit: Unit("s"),
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "oor-min",
|
|
args: args{
|
|
dur: -1,
|
|
offset: TimestampToVal(Unit("s"), MinTimestamp),
|
|
unit: Unit("s"),
|
|
},
|
|
wantErr: true,
|
|
errStr: errTimeOutsideEpoch,
|
|
},
|
|
{
|
|
name: "min-oor",
|
|
args: args{
|
|
dur: 0,
|
|
offset: TimestampToVal(Unit("s"), MinTimestamp.Add(-1*time.Second)),
|
|
unit: Unit("s"),
|
|
},
|
|
wantErr: true,
|
|
errStr: errTimeOutsideEpoch,
|
|
},
|
|
{
|
|
name: "max-max",
|
|
args: args{
|
|
dur: 0,
|
|
offset: TimestampToVal(Unit("ns"), MaxTimestampNano),
|
|
unit: Unit("ns"),
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "oor-max",
|
|
args: args{
|
|
dur: 1,
|
|
offset: TimestampToVal(Unit("ns"), MaxTimestampNano),
|
|
unit: Unit("ns"),
|
|
},
|
|
wantErr: true,
|
|
errStr: errTimeOutsideEpoch,
|
|
},
|
|
{
|
|
name: "max-oor",
|
|
args: args{
|
|
dur: 0,
|
|
offset: TimestampToVal(Unit("ns"), MaxTimestampNano.Add(1*time.Nanosecond)),
|
|
unit: Unit("ns"),
|
|
},
|
|
wantErr: true,
|
|
errStr: errTimeOutsideEpoch,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := validateDuration(tt.args.dur, tt.args.offset, tt.args.unit); (err != nil) != tt.wantErr {
|
|
if !strings.Contains(err.Error(), tt.errStr) {
|
|
t.Errorf("TimestampField.validateDuration() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateTimestamp(t *testing.T) {
|
|
type args struct {
|
|
unit Unit
|
|
ts time.Time
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "nano-max",
|
|
args: args{
|
|
unit: Unit("ns"),
|
|
ts: MaxTimestampNano,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "nano-min",
|
|
args: args{
|
|
unit: Unit("ns"),
|
|
ts: MinTimestampNano,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "nano-max-oor",
|
|
args: args{
|
|
unit: Unit("ns"),
|
|
ts: MaxTimestampNano.Add(1 * time.Nanosecond),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "nano-min-oor",
|
|
args: args{
|
|
unit: Unit("ns"),
|
|
ts: MinTimestampNano.Add(-1 * time.Nanosecond),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "ms-max",
|
|
args: args{
|
|
unit: Unit("ms"),
|
|
ts: MaxTimestamp,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ms-min",
|
|
args: args{
|
|
unit: Unit("ms"),
|
|
ts: MinTimestamp,
|
|
},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "ms-max-oor",
|
|
args: args{
|
|
unit: Unit("ms"),
|
|
ts: MaxTimestamp.Add(1 * time.Millisecond),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "ms-min-oor",
|
|
args: args{
|
|
unit: Unit("ms"),
|
|
ts: MinTimestamp.Add(-1 * time.Millisecond),
|
|
},
|
|
wantErr: true,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if err := validateTimestamp(tt.args.unit, tt.args.ts); (err != nil) != tt.wantErr {
|
|
t.Errorf("validateTimestamp() error = %v, wantErr %v", err, tt.wantErr)
|
|
}
|
|
})
|
|
}
|
|
}
|