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(segments): Segment AND'ing #1915

Merged
merged 57 commits into from
Aug 15, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
206b5cc
feat(segments): POC of API parity with segment anding
yquansah Jul 27, 2023
4946e53
feat(segment_anding): feature parity fixes and new logic for and-ing
yquansah Jul 27, 2023
4868a4b
feat(rollouts): feature parity with rollouts
yquansah Jul 28, 2023
777f817
feat(rollouts): Additional functionality working with multiple segmen…
yquansah Jul 28, 2023
56e7b68
feat(rules/rollouts): Add tests for server and storage
yquansah Jul 28, 2023
3cd24eb
chore(rollouts): evaluation storage tests
yquansah Jul 28, 2023
629c048
feat: Merge main and make relevant fixes
yquansah Jul 28, 2023
c8d13e2
feat(fs): Add FS implementation of segment AND-ing
yquansah Jul 28, 2023
b74affb
chore(tests): Fix all tests after FS implementation
yquansah Jul 28, 2023
e5198e1
chore(importer): Account for segmentKeys in importer
yquansah Jul 28, 2023
49001ff
chore(testing): Add integration tests for segment AND-ing feature
yquansah Jul 31, 2023
8e7bc82
feat(dbs): Add additional database DDL
yquansah Jul 31, 2023
efc0013
Merge branch 'main' into segment-anding
yquansah Jul 31, 2023
d00ad9c
chore: move alter statements to separate file
yquansah Aug 1, 2023
dd803a9
Merge remote-tracking branch 'origin/segment-anding' into segment-anding
yquansah Aug 1, 2023
80644af
feat(update): Add update methods for rollouts and rules
yquansah Aug 1, 2023
17af43d
chore(rollback): Rollback transaction in a defer
yquansah Aug 1, 2023
f847fc0
feat(update): Tests for UpdateRule and fixing linter errors
yquansah Aug 1, 2023
a7dec84
Merge remote-tracking branch 'origin/main' into segment-anding
yquansah Aug 1, 2023
2101228
chore(tests/rules/rollouts): fixing overall tests
yquansah Aug 1, 2023
ec2e991
chore(style): add better length checks
yquansah Aug 1, 2023
36b728b
feat(EvaluationResponse): Add SegmentKeys to legacy evaluation response
yquansah Aug 1, 2023
b28c7c9
chore(testing): Add tests for readonly on updated import/export format
yquansah Aug 1, 2023
901a927
chore: get import/export integration test working
yquansah Aug 2, 2023
72dab7e
chore(mysql): fix mysql test
yquansah Aug 3, 2023
d0b4ebf
Merge branch 'main' into segment-anding
yquansah Aug 3, 2023
d53550e
chore(storage): Do a length check of 1
yquansah Aug 3, 2023
f9801b3
chore: fix sqlite migrations
yquansah Aug 3, 2023
ceb7d5c
Merge remote-tracking branch 'origin/segment-anding' into segment-anding
yquansah Aug 3, 2023
9e20cb9
chore(rollouts): Fixes and removing of print statement
yquansah Aug 3, 2023
5878a6f
Merge branch 'main' into segment-anding
yquansah Aug 3, 2023
21088db
feat(segment-anding): Create RuleSegments and RolloutSegments (#1941)
yquansah Aug 4, 2023
5b864bb
Merge branch 'main' into segment-anding
yquansah Aug 4, 2023
bf420e0
feat(sql): Add unique constraints on new tables
yquansah Aug 5, 2023
e86bc13
Merge remote-tracking branch 'origin/segment-anding' into segment-anding
yquansah Aug 5, 2023
93508db
feat(rollouts/rules): Add SegmentKeysUnaryInterceptor for relevant re…
yquansah Aug 6, 2023
811faa6
chore(rules/rollouts): fix up integration tests
yquansah Aug 6, 2023
8c1d4bc
chore: last cleanup move back to non-interceptor
yquansah Aug 7, 2023
d0422a0
Merge branch 'main' into segment-anding
yquansah Aug 7, 2023
47a8dd8
Merge branch 'main' into segment-anding
yquansah Aug 7, 2023
3f81765
feat(rollouts/rules): UI for rollout/rule editing of segments (#1953)
yquansah Aug 8, 2023
a5088ce
chore: fix merge conflict
yquansah Aug 8, 2023
c3870c1
chore: fix reset of rules/rollouts (#1974)
yquansah Aug 9, 2023
45149d6
feat(rules/rollouts): hide or and and based on segment keys length, a…
yquansah Aug 9, 2023
190b3cd
Merge branch 'main' into segment-anding
yquansah Aug 10, 2023
524f277
feat(rules): Make segment be one of two types (#1978)
yquansah Aug 10, 2023
7db60c2
feat(rules): Revise query for optimization (#1979)
yquansah Aug 10, 2023
78b4995
Merge branch 'main' into segment-anding
yquansah Aug 10, 2023
b4b00cc
Merge branch 'main' into segment-anding
yquansah Aug 11, 2023
1139322
chore(evaluation): Use JSON tags for EvaluationSegment (#1984)
yquansah Aug 11, 2023
0856141
feat(segments): introduce cue validation for new segment definitions …
yquansah Aug 14, 2023
59dd229
chore(cockroach): Remove sql_safe_update setting
yquansah Aug 14, 2023
48cfbfd
Merge remote-tracking branch 'origin/segment-anding' into segment-anding
yquansah Aug 14, 2023
f04dd9c
chore: merge main branch in
yquansah Aug 14, 2023
6dee605
chore: use SegmentKey for backwards compatibility
yquansah Aug 14, 2023
6b535e9
chore: address comments related to squirrel statements
yquansah Aug 15, 2023
b5c10ce
Merge branch 'main' into segment-anding
yquansah Aug 15, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
176 changes: 168 additions & 8 deletions build/testing/integration/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,6 +350,64 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
assert.Equal(t, "eq", updatedConstraint.Operator)
assert.Equal(t, "baz", updatedConstraint.Value)
assert.Equal(t, "newdesc", updatedConstraint.Description)

t.Log(`Create two additional segments.`)

createdSegment, err = client.Flipt().CreateSegment(ctx, &flipt.CreateSegmentRequest{
NamespaceKey: namespace,
Key: "segment",
Name: "Segment",
MatchType: flipt.MatchType_ALL_MATCH_TYPE,
})

require.NoError(t, err)

assert.Equal(t, "segment", createdSegment.Key)
assert.Equal(t, "Segment", createdSegment.Name)
assert.Equal(t, flipt.MatchType_ALL_MATCH_TYPE, createdSegment.MatchType)

createdConstraint, err := client.Flipt().CreateConstraint(ctx, &flipt.CreateConstraintRequest{
NamespaceKey: namespace,
SegmentKey: createdSegment.Key,
Type: flipt.ComparisonType_STRING_COMPARISON_TYPE,
Property: "first",
Operator: "eq",
Value: "segment",
})

require.NoError(t, err)

assert.Equal(t, "first", createdConstraint.Property)
assert.Equal(t, "eq", createdConstraint.Operator)
assert.Equal(t, "segment", createdConstraint.Value)

createdSegment, err = client.Flipt().CreateSegment(ctx, &flipt.CreateSegmentRequest{
NamespaceKey: namespace,
Key: "another-segment",
Name: "Another Segment",
MatchType: flipt.MatchType_ALL_MATCH_TYPE,
})

require.NoError(t, err)

assert.Equal(t, "another-segment", createdSegment.Key)
assert.Equal(t, "Another Segment", createdSegment.Name)
assert.Equal(t, flipt.MatchType_ALL_MATCH_TYPE, createdSegment.MatchType)

createdConstraint, err = client.Flipt().CreateConstraint(ctx, &flipt.CreateConstraintRequest{
NamespaceKey: namespace,
SegmentKey: createdSegment.Key,
Type: flipt.ComparisonType_STRING_COMPARISON_TYPE,
Property: "second",
Operator: "eq",
Value: "another-segment",
})

require.NoError(t, err)

assert.Equal(t, "second", createdConstraint.Property)
assert.Equal(t, "eq", createdConstraint.Operator)
assert.Equal(t, "another-segment", createdConstraint.Value)
})

t.Run("Rules and Distributions", func(t *testing.T) {
Expand Down Expand Up @@ -396,6 +454,23 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
assert.Equal(t, "everyone", ruleTwo.SegmentKey)
assert.Equal(t, int32(2), ruleTwo.Rank)

t.Log(`Create rule "rank 3".`)

ruleThree, err := client.Flipt().CreateRule(ctx, &flipt.CreateRuleRequest{
NamespaceKey: namespace,
FlagKey: "test",
SegmentKeys: []string{"segment", "another-segment"},
SegmentOperator: flipt.SegmentOperator_AND_SEGMENT_OPERATOR,
Rank: 3,
})

require.NoError(t, err)

assert.Equal(t, "test", ruleThree.FlagKey)
assert.Contains(t, ruleThree.SegmentKeys, "segment")
assert.Contains(t, ruleThree.SegmentKeys, "another-segment")
assert.Equal(t, int32(3), ruleThree.Rank)

// ensure you can not link flags and segments from different namespaces.
if !namespaceIsDefault(namespace) {
t.Log(`Ensure that rules can only link entities in the same namespace.`)
Expand Down Expand Up @@ -427,17 +502,18 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
})
require.NoError(t, err)

assert.Len(t, allRules.Rules, 2)
assert.Len(t, allRules.Rules, 3)

assert.Equal(t, ruleOne.Id, allRules.Rules[0].Id)
assert.Equal(t, ruleTwo.Id, allRules.Rules[1].Id)
assert.Equal(t, ruleThree.Id, allRules.Rules[2].Id)

t.Log(`Re-order rules.`)

err = client.Flipt().OrderRules(ctx, &flipt.OrderRulesRequest{
NamespaceKey: namespace,
FlagKey: "test",
RuleIds: []string{ruleTwo.Id, ruleOne.Id},
RuleIds: []string{ruleTwo.Id, ruleOne.Id, ruleThree.Id},
})
require.NoError(t, err)

Expand All @@ -449,13 +525,15 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
})
require.NoError(t, err)

assert.Len(t, allRules.Rules, 2)
assert.Len(t, allRules.Rules, 3)

// ensure the order has switched
assert.Equal(t, ruleTwo.Id, allRules.Rules[0].Id)
assert.Equal(t, int32(1), allRules.Rules[0].Rank)
assert.Equal(t, ruleOne.Id, allRules.Rules[1].Id)
assert.Equal(t, int32(2), allRules.Rules[1].Rank)
assert.Equal(t, ruleThree.Id, allRules.Rules[2].Id)
assert.Equal(t, int32(3), allRules.Rules[2].Rank)

t.Log(`Create distribution "rollout 100".`)

Expand Down Expand Up @@ -532,6 +610,15 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au

assert.Equal(t, ruleTwo.Id, distribution.RuleId)
assert.Equal(t, float32(100), distribution.Rollout)

_, err = client.Flipt().CreateDistribution(ctx, &flipt.CreateDistributionRequest{
NamespaceKey: namespace,
FlagKey: "test",
RuleId: ruleThree.Id,
VariantId: flag.Variants[0].Id,
Rollout: 100,
})
require.NoError(t, err)
})

t.Run("Boolean Rollouts", func(t *testing.T) {
Expand Down Expand Up @@ -571,11 +658,32 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
assert.Equal(t, "everyone", rolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.SegmentKey)
assert.Equal(t, true, rolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.Value)

anotherRolloutSegment, err := client.Flipt().CreateRollout(ctx, &flipt.CreateRolloutRequest{
NamespaceKey: namespace,
FlagKey: "boolean_disabled",
Description: "matches a segment",
Rank: 2,
Rule: &flipt.CreateRolloutRequest_Segment{
Segment: &flipt.RolloutSegment{
SegmentKeys: []string{"segment", "another-segment"},
Value: false,
},
},
})
require.NoError(t, err)

assert.Equal(t, namespace, anotherRolloutSegment.NamespaceKey)
assert.Equal(t, "boolean_disabled", anotherRolloutSegment.FlagKey)
assert.Equal(t, int32(2), anotherRolloutSegment.Rank)
assert.Contains(t, anotherRolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.SegmentKeys, "segment")
assert.Contains(t, anotherRolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.SegmentKeys, "another-segment")
assert.Equal(t, false, anotherRolloutSegment.Rule.(*flipt.Rollout_Segment).Segment.Value)

rolloutThreshold, err := client.Flipt().CreateRollout(ctx, &flipt.CreateRolloutRequest{
NamespaceKey: namespace,
FlagKey: "boolean_disabled",
Description: "50% disabled",
Rank: 2,
Rank: 3,
Rule: &flipt.CreateRolloutRequest_Threshold{
Threshold: &flipt.RolloutThreshold{
Percentage: 50,
Expand All @@ -588,7 +696,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
assert.Equal(t, namespace, rolloutThreshold.NamespaceKey)
assert.Equal(t, "boolean_disabled", rolloutThreshold.FlagKey)
assert.Equal(t, "50% disabled", rolloutThreshold.Description)
assert.Equal(t, int32(2), rolloutThreshold.Rank)
assert.Equal(t, int32(3), rolloutThreshold.Rank)
assert.Equal(t, float32(50.0), rolloutThreshold.Rule.(*flipt.Rollout_Threshold).Threshold.Percentage)
assert.Equal(t, true, rolloutThreshold.Rule.(*flipt.Rollout_Threshold).Threshold.Value)

Expand Down Expand Up @@ -634,6 +742,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au

assert.Empty(t, cmp.Diff([]*flipt.Rollout{
rolloutSegment,
anotherRolloutSegment,
rolloutThreshold,
}, rollouts.Rules, protocmp.Transform()))

Expand Down Expand Up @@ -668,7 +777,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
assert.Equal(t, namespace, updatedRollout.NamespaceKey)
assert.Equal(t, "boolean_disabled", updatedRollout.FlagKey)
assert.Equal(t, "50% enabled", updatedRollout.Description)
assert.Equal(t, int32(2), updatedRollout.Rank)
assert.Equal(t, int32(3), updatedRollout.Rank)
assert.Equal(t, float32(50.0), updatedRollout.Rule.(*flipt.Rollout_Threshold).Threshold.Percentage)
assert.Equal(t, false, updatedRollout.Rule.(*flipt.Rollout_Threshold).Threshold.Value)

Expand Down Expand Up @@ -778,7 +887,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au

t.Run("Evaluation", func(t *testing.T) {
t.Run("Variant", func(t *testing.T) {
t.Run("successful match", func(t *testing.T) {
t.Run("successful match (rank 1)", func(t *testing.T) {
result, err := client.Evaluation().Variant(ctx, &evaluation.EvaluationRequest{
NamespaceKey: namespace,
FlagKey: "test",
Expand All @@ -796,6 +905,25 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason)
})

t.Run("successful match (rank 3)", func(t *testing.T) {
result, err := client.Evaluation().Variant(ctx, &evaluation.EvaluationRequest{
NamespaceKey: namespace,
FlagKey: "test",
EntityId: uuid.Must(uuid.NewV4()).String(),
Context: map[string]string{
"first": "segment",
"second": "another-segment",
},
})
require.NoError(t, err)

require.True(t, result.Match, "Evaluation should have matched.")
assert.Contains(t, result.SegmentKeys, "segment")
assert.Contains(t, result.SegmentKeys, "another-segment")
assert.Equal(t, "one", result.VariantKey)
assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason)
})

t.Run("no match", func(t *testing.T) {
result, err := client.Evaluation().Variant(ctx, &evaluation.EvaluationRequest{
NamespaceKey: namespace,
Expand Down Expand Up @@ -874,7 +1002,7 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
assert.False(t, result.Enabled, "value should be threshold match value")
})

t.Run("segment match", func(t *testing.T) {
t.Run("segment match (rank 1)", func(t *testing.T) {
result, err := client.Evaluation().Boolean(ctx, &evaluation.EvaluationRequest{
NamespaceKey: namespace,
FlagKey: "boolean_disabled",
Expand All @@ -890,6 +1018,23 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason)
assert.True(t, result.Enabled, "value should be segment match value")
})

t.Run("segment match (rank 2)", func(t *testing.T) {
result, err := client.Evaluation().Boolean(ctx, &evaluation.EvaluationRequest{
NamespaceKey: namespace,
FlagKey: "boolean_disabled",
EntityId: "fixed",
Context: map[string]string{
"first": "segment",
"second": "another-segment",
},
})

require.NoError(t, err)

assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, result.Reason)
assert.False(t, result.Enabled, "value should be segment match value")
})
})

t.Run("Batch", func(t *testing.T) {
Expand Down Expand Up @@ -995,6 +1140,21 @@ func API(t *testing.T, ctx context.Context, client sdk.SDK, namespace string, au
NamespaceKey: namespace,
Key: "everyone",
})

require.NoError(t, err)

err = client.Flipt().DeleteSegment(ctx, &flipt.DeleteSegmentRequest{
NamespaceKey: namespace,
Key: "segment",
})

require.NoError(t, err)

err = client.Flipt().DeleteSegment(ctx, &flipt.DeleteSegmentRequest{
NamespaceKey: namespace,
Key: "another-segment",
})

require.NoError(t, err)

if !namespaceIsDefault(namespace) {
Expand Down
2 changes: 2 additions & 0 deletions build/testing/integration/readonly/readonly_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,7 @@ func TestReadOnly(t *testing.T) {
assert.Equal(t, namespace, rule.NamespaceKey)
assert.Equal(t, "flag_boolean", rule.FlagKey)
assert.Equal(t, "segment_001", rule.GetSegment().SegmentKey)

assert.True(t, rule.GetSegment().Value)
assert.NotEmpty(t, rule.Id)
assert.Equal(t, int32(1), rule.Rank)
Expand Down Expand Up @@ -441,6 +442,7 @@ func TestReadOnly(t *testing.T) {
assert.Equal(t, true, response.Match)
assert.Equal(t, "variant_002", response.VariantKey)
assert.Equal(t, evaluation.EvaluationReason_MATCH_EVALUATION_REASON, response.Reason)
assert.Contains(t, response.SegmentKeys, "segment_005")
})

t.Run("no match", func(t *testing.T) {
Expand Down
19 changes: 19 additions & 0 deletions config/migrations/cockroachdb/8_segment_anding_tables.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
-- Rules
CREATE TABLE IF NOT EXISTS rule_segments (
rule_id VARCHAR(255) NOT NULL REFERENCES rules ON DELETE CASCADE,
namespace_key VARCHAR(255) NOT NULL,
segment_key VARCHAR(255) NOT NULL,
FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, key) ON DELETE CASCADE
);

INSERT INTO rule_segments (rule_id, namespace_key, segment_key) SELECT id AS rule_id, namespace_key, segment_key FROM rules;

-- Rollouts
CREATE TABLE IF NOT EXISTS rollout_segment_references (
rollout_segment_id VARCHAR(255) NOT NULL REFERENCES rollout_segments ON DELETE CASCADE,
namespace_key VARCHAR(255) NOT NULL,
segment_key VARCHAR(255) NOT NULL,
FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, key) ON DELETE CASCADE
);

INSERT INTO rollout_segment_references (rollout_segment_id, namespace_key, segment_key) SELECT id AS rollout_segment_id, namespace_key, segment_key FROM rollout_segments;
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
SET sql_safe_updates = false;

-- Rules
ALTER TABLE IF EXISTS rules DROP CONSTRAINT fk_namespace_key_ref_segments;

ALTER TABLE IF EXISTS rules DROP COLUMN segment_key;

ALTER TABLE IF EXISTS rules ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0;

-- Rollouts
ALTER TABLE IF EXISTS rollout_segments DROP CONSTRAINT fk_namespace_key_ref_segments;
ALTER TABLE IF EXISTS rollout_segments DROP CONSTRAINT fk_namespace_key_ref_namespaces;

ALTER TABLE IF EXISTS rollout_segments DROP COLUMN segment_key;
ALTER TABLE IF EXISTS rollout_segments DROP COLUMN namespace_key;

ALTER TABLE IF EXISTS rollout_segments ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0;

SET sql_safe_updates = true;
15 changes: 15 additions & 0 deletions config/migrations/mysql/10_alter_rules_rollout_segments.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-- Rules
ALTER TABLE rules DROP FOREIGN KEY `rules_ibfk_3`;

ALTER TABLE rules DROP COLUMN segment_key;

ALTER TABLE rules ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0;

-- Rollouts
ALTER TABLE rollout_segments DROP FOREIGN KEY `rollout_segments_ibfk_1`;
ALTER TABLE rollout_segments DROP FOREIGN KEY `rollout_segments_ibfk_3`;

ALTER TABLE rollout_segments DROP COLUMN segment_key;
ALTER TABLE rollout_segments DROP COLUMN namespace_key;

ALTER TABLE rollout_segments ADD COLUMN segment_operator INTEGER NOT NULL DEFAULT 0;
21 changes: 21 additions & 0 deletions config/migrations/mysql/9_segment_anding_tables.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
-- Rules
CREATE TABLE IF NOT EXISTS rule_segments (
rule_id VARCHAR(255) NOT NULL,
namespace_key VARCHAR(255) NOT NULL,
segment_key VARCHAR(255) NOT NULL,
FOREIGN KEY (rule_id) REFERENCES rules (id) ON DELETE CASCADE,
FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, `key`) ON DELETE CASCADE
);

INSERT INTO rule_segments (rule_id, namespace_key, segment_key) SELECT id AS rule_id, namespace_key, segment_key FROM rules;

-- Rollouts
CREATE TABLE IF NOT EXISTS rollout_segment_references (
rollout_segment_id VARCHAR(255) NOT NULL,
namespace_key VARCHAR(255) NOT NULL,
segment_key VARCHAR(255) NOT NULL,
FOREIGN KEY (rollout_segment_id) REFERENCES rollout_segments (id) ON DELETE CASCADE,
FOREIGN KEY (namespace_key, segment_key) REFERENCES segments (namespace_key, `key`) ON DELETE CASCADE
);

INSERT INTO rollout_segment_references (rollout_segment_id, namespace_key, segment_key) SELECT id AS rollout_segment_id, namespace_key, segment_key FROM rollout_segments;
Loading