Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: (sqlite) support namespace at storage level, mostly for flags #1368

Merged
merged 13 commits into from
Mar 1, 2023
1 change: 1 addition & 0 deletions config/migrations/sqlite3/6_create_namespaces.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/* TODO */
85 changes: 85 additions & 0 deletions config/migrations/sqlite3/6_create_namespaces.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
PRAGMA foreign_keys=off;

/* Create namespaces table */
CREATE TABLE IF NOT EXISTS namespaces (
key VARCHAR(255) PRIMARY KEY UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
protected BOOLEAN DEFAULT FALSE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);

/* Create default namespace */
INSERT INTO namespaces (key, name, description, protected) VALUES ('default', 'Default', 'Default namespace', true);

/* SQLite doesn't allow you to drop unique constraints with ALTER TABLE
so we have to create a new table with the schema we want and copy the data over.
https://www.sqlite.org/lang_altertable.html
*/

/* Create temp tables */

/* flags */
CREATE TABLE IF NOT EXISTS flags_temp (
key VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
enabled BOOLEAN DEFAULT FALSE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
namespace_key VARCHAR(255) NOT NULL DEFAULT 'default' REFERENCES namespaces ON DELETE CASCADE,
PRIMARY KEY (namespace_key, key)); /* composite primary key unique by default */

INSERT INTO flags_temp (key, name, description, enabled, created_at, updated_at)
SELECT key, name, description, enabled, created_at, updated_at
FROM flags;

DROP TABLE flags;

ALTER TABLE flags_temp RENAME TO flags;

/* variants */
CREATE TABLE IF NOT EXISTS variants_temp (
id VARCHAR(255) PRIMARY KEY UNIQUE NOT NULL,
flag_key VARCHAR(255) NOT NULL,
key VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT NOT NULL,
attachment TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
namespace_key VARCHAR(255) NOT NULL DEFAULT 'default' REFERENCES namespaces ON DELETE CASCADE,
UNIQUE (namespace_key, flag_key, key),
FOREIGN KEY (namespace_key, flag_key) REFERENCES flags (namespace_key, key) ON DELETE CASCADE
);

INSERT INTO variants_temp (id, flag_key, key, name, description, attachment, created_at, updated_at)
SELECT id, flag_key, key, name, description, attachment, created_at, updated_at
FROM variants;

DROP TABLE variants;

ALTER TABLE variants_temp RENAME TO variants;

/* rules */
CREATE TABLE IF NOT EXISTS rules_temp (
id VARCHAR(255) PRIMARY KEY UNIQUE NOT NULL,
flag_key VARCHAR(255) NOT NULL,
segment_key VARCHAR(255) NOT NULL REFERENCES segments ON DELETE CASCADE,
rank INTEGER DEFAULT 1 NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
namespace_key VARCHAR(255) NOT NULL DEFAULT 'default' REFERENCES namespaces ON DELETE CASCADE,
FOREIGN KEY (namespace_key, flag_key) REFERENCES flags (namespace_key, key) ON DELETE CASCADE
);

INSERT INTO rules_temp (id, flag_key, segment_key, rank, created_at, updated_at)
SELECT id, flag_key, segment_key, rank, created_at, updated_at
FROM rules;

DROP TABLE rules;

ALTER TABLE rules_temp RENAME TO rules;

PRAGMA foreign_keys=on;
6 changes: 4 additions & 2 deletions internal/ext/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
const defaultBatchSize = 25

type lister interface {
ListFlags(ctx context.Context, opts ...storage.QueryOption) (storage.ResultSet[*flipt.Flag], error)
ListFlags(ctx context.Context, namespaceKey string, opts ...storage.QueryOption) (storage.ResultSet[*flipt.Flag], error)
ListSegments(ctx context.Context, opts ...storage.QueryOption) (storage.ResultSet[*flipt.Segment], error)
ListRules(ctx context.Context, flagKey string, opts ...storage.QueryOption) (storage.ResultSet[*flipt.Rule], error)
}
Expand Down Expand Up @@ -45,9 +45,11 @@ func (e *Exporter) Export(ctx context.Context, w io.Writer) error {
nextPage string
)

// TODO: support all namespaces

// export flags/variants in batches
for batch := uint64(0); remaining; batch++ {
resp, err := e.store.ListFlags(ctx, storage.WithPageToken(nextPage), storage.WithLimit(batchSize))
resp, err := e.store.ListFlags(ctx, storage.DefaultNamespace, storage.WithPageToken(nextPage), storage.WithLimit(batchSize))
if err != nil {
return fmt.Errorf("getting flags: %w", err)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/ext/exporter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type mockLister struct {
ruleErr error
}

func (m mockLister) ListFlags(ctx context.Context, opts ...storage.QueryOption) (storage.ResultSet[*flipt.Flag], error) {
func (m mockLister) ListFlags(ctx context.Context, namespaceKey string, opts ...storage.QueryOption) (storage.ResultSet[*flipt.Flag], error) {
return storage.ResultSet[*flipt.Flag]{
Results: m.flags,
}, m.flagErr
Expand Down
2 changes: 1 addition & 1 deletion internal/server/evaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ func (s *Server) evaluate(ctx context.Context, r *flipt.EvaluationRequest) (resp
FlagKey: r.FlagKey,
}

flag, err := s.store.GetFlag(ctx, r.FlagKey)
flag, err := s.store.GetFlag(ctx, r.NamespaceKey, r.FlagKey)
if err != nil {
resp.Reason = flipt.EvaluationReason_ERROR_EVALUATION_REASON

Expand Down
52 changes: 26 additions & 26 deletions internal/server/evaluator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ func TestBatchEvaluate(t *testing.T) {
Key: "bar",
Enabled: false,
}
store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, "bar").Return(disabled, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "bar").Return(disabled, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return([]*storage.EvaluationRule{}, nil)

Expand Down Expand Up @@ -82,9 +82,9 @@ func TestBatchEvaluate_FlagNotFoundExcluded(t *testing.T) {
Key: "bar",
Enabled: false,
}
store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, "bar").Return(disabled, nil)
store.On("GetFlag", mock.Anything, "NotFoundFlag").Return(&flipt.Flag{}, errs.ErrNotFoundf("flag %q", "NotFoundFlag"))
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "bar").Return(disabled, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "NotFoundFlag").Return(&flipt.Flag{}, errs.ErrNotFoundf("flag %q", "NotFoundFlag"))

store.On("GetEvaluationRules", mock.Anything, "foo").Return([]*storage.EvaluationRule{}, nil)

Expand Down Expand Up @@ -130,9 +130,9 @@ func TestBatchEvaluate_FlagNotFound(t *testing.T) {
Key: "bar",
Enabled: false,
}
store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, "bar").Return(disabled, nil)
store.On("GetFlag", mock.Anything, "NotFoundFlag").Return(&flipt.Flag{}, errs.ErrNotFoundf("flag %q", "NotFoundFlag"))
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "bar").Return(disabled, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "NotFoundFlag").Return(&flipt.Flag{}, errs.ErrNotFoundf("flag %q", "NotFoundFlag"))

store.On("GetEvaluationRules", mock.Anything, "foo").Return([]*storage.EvaluationRule{}, nil)

Expand Down Expand Up @@ -172,7 +172,7 @@ func TestEvaluate_FlagNotFound(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(&flipt.Flag{}, errs.ErrNotFoundf("flag %q", "foo"))
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(&flipt.Flag{}, errs.ErrNotFoundf("flag %q", "foo"))

resp, err := s.Evaluate(context.TODO(), &flipt.EvaluationRequest{
EntityId: "1",
Expand All @@ -198,7 +198,7 @@ func TestEvaluate_FlagDisabled(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(disabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(disabledFlag, nil)

resp, err := s.Evaluate(context.TODO(), &flipt.EvaluationRequest{
EntityId: "1",
Expand All @@ -223,7 +223,7 @@ func TestEvaluate_FlagNoRules(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return([]*storage.EvaluationRule{}, nil)

Expand All @@ -250,7 +250,7 @@ func TestEvaluate_ErrorGettingRules(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return([]*storage.EvaluationRule{}, errors.New("error getting rules!"))

Expand All @@ -277,7 +277,7 @@ func TestEvaluate_RulesOutOfOrder(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -339,7 +339,7 @@ func TestEvaluate_ErrorGettingDistributions(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -387,7 +387,7 @@ func TestEvaluate_MatchAll_NoVariants_NoDistributions(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -476,7 +476,7 @@ func TestEvaluate_MatchAll_SingleVariantDistribution(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -606,7 +606,7 @@ func TestEvaluate_MatchAll_RolloutDistribution(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -727,7 +727,7 @@ func TestEvaluate_MatchAll_RolloutDistribution_MultiRule(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -802,7 +802,7 @@ func TestEvaluate_MatchAll_NoConstraints(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -913,7 +913,7 @@ func TestEvaluate_MatchAny_NoVariants_NoDistributions(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -1002,7 +1002,7 @@ func TestEvaluate_MatchAny_SingleVariantDistribution(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -1164,7 +1164,7 @@ func TestEvaluate_MatchAny_RolloutDistribution(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -1285,7 +1285,7 @@ func TestEvaluate_MatchAny_RolloutDistribution_MultiRule(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -1360,7 +1360,7 @@ func TestEvaluate_MatchAny_NoConstraints(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -1471,7 +1471,7 @@ func TestEvaluate_FirstRolloutRuleIsZero(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down Expand Up @@ -1571,7 +1571,7 @@ func TestEvaluate_MultipleZeroRolloutDistributions(t *testing.T) {
}
)

store.On("GetFlag", mock.Anything, "foo").Return(enabledFlag, nil)
store.On("GetFlag", mock.Anything, mock.Anything, "foo").Return(enabledFlag, nil)

store.On("GetEvaluationRules", mock.Anything, "foo").Return(
[]*storage.EvaluationRule{
Expand Down
6 changes: 3 additions & 3 deletions internal/server/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import (
// GetFlag gets a flag
func (s *Server) GetFlag(ctx context.Context, r *flipt.GetFlagRequest) (*flipt.Flag, error) {
s.logger.Debug("get flag", zap.Stringer("request", r))
flag, err := s.store.GetFlag(ctx, r.Key)
flag, err := s.store.GetFlag(ctx, r.NamespaceKey, r.Key)

spanAttrs := []attribute.KeyValue{
fliptotel.AttributeFlag.String(r.Key),
Expand Down Expand Up @@ -57,7 +57,7 @@ func (s *Server) ListFlags(ctx context.Context, r *flipt.ListFlagRequest) (*flip
opts = append(opts, storage.WithOffset(uint64(r.Offset)))
}

results, err := s.store.ListFlags(ctx, opts...)
results, err := s.store.ListFlags(ctx, r.NamespaceKey, opts...)
if err != nil {
return nil, err
}
Expand All @@ -66,7 +66,7 @@ func (s *Server) ListFlags(ctx context.Context, r *flipt.ListFlagRequest) (*flip

resp.Flags = append(resp.Flags, results.Results...)

total, err := s.store.CountFlags(ctx)
total, err := s.store.CountFlags(ctx, r.NamespaceKey)
if err != nil {
return nil, err
}
Expand Down
Loading