Skip to content

Commit f701893

Browse files
authored
[FD-5510] feat: Adding payload version and ID in the project table (#699)
* Adding payload version and ID in the project table * format: * Update min payload version * test fix
1 parent 42a956e commit f701893

10 files changed

Lines changed: 107 additions & 10 deletions

File tree

internal/dev_server/db/sqlite.go

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"encoding/json"
77
"io"
88
"os"
9+
"strings"
910

1011
_ "github.com/mattn/go-sqlite3"
1112
"github.com/pkg/errors"
@@ -47,12 +48,15 @@ func (s *Sqlite) GetDevProject(ctx context.Context, key string) (*model.Project,
4748
var flagStateData string
4849

4950
row := s.database.QueryRowContext(ctx, `
50-
SELECT key, source_environment_key, context, last_sync_time, flag_state
51-
FROM projects
51+
SELECT key, source_environment_key, context, last_sync_time, flag_state, payload_version
52+
FROM projects
5253
WHERE key = ?
5354
`, key)
5455

55-
if err := row.Scan(&project.Key, &project.SourceEnvironmentKey, &contextData, &project.LastSyncTime, &flagStateData); err != nil {
56+
if err := row.Scan(
57+
&project.Key, &project.SourceEnvironmentKey, &contextData,
58+
&project.LastSyncTime, &flagStateData, &project.PayloadVersion,
59+
); err != nil {
5660
if errors.Is(err, sql.ErrNoRows) {
5761
return nil, model.NewErrNotFound("project", key)
5862
}
@@ -200,14 +204,15 @@ SELECT 1 FROM projects WHERE key = ?
200204
return
201205
}
202206
_, err = tx.Exec(`
203-
INSERT INTO projects (key, source_environment_key, context, last_sync_time, flag_state)
204-
VALUES (?, ?, ?, ?, ?)
207+
INSERT INTO projects (key, source_environment_key, context, last_sync_time, flag_state, payload_version)
208+
VALUES (?, ?, ?, ?, ?, ?)
205209
`,
206210
project.Key,
207211
project.SourceEnvironmentKey,
208212
project.Context.JSONString(),
209213
project.LastSyncTime,
210214
string(flagsStateJson),
215+
project.PayloadVersion,
211216
)
212217
if err != nil {
213218
return
@@ -341,6 +346,20 @@ func (s *Sqlite) UpsertOverride(ctx context.Context, override model.Override) (m
341346
return override, nil
342347
}
343348

349+
func (s *Sqlite) IncrementProjectPayloadVersion(ctx context.Context, projectKey string) (int, error) {
350+
row := s.database.QueryRowContext(ctx, `
351+
UPDATE projects
352+
SET payload_version = payload_version + 1
353+
WHERE key = ?
354+
RETURNING payload_version
355+
`, projectKey)
356+
var version int
357+
if err := row.Scan(&version); err != nil {
358+
return 0, errors.Wrap(err, "unable to increment payload version")
359+
}
360+
return version, nil
361+
}
362+
344363
func (s *Sqlite) DeactivateOverride(ctx context.Context, projectKey, flagKey string) (int, error) {
345364
row := s.database.QueryRowContext(ctx, `
346365
UPDATE overrides
@@ -373,12 +392,12 @@ func (s *Sqlite) RestoreBackup(ctx context.Context, stream io.Reader) (string, e
373392
}
374393
err = os.Rename(filepath, s.dbPath)
375394
if err != nil {
376-
//panic because this would really leave the app in an invalid state
395+
// panic because this would really leave the app in an invalid state
377396
panic(err)
378397
}
379398
s.database, err = sql.Open("sqlite3", s.dbPath)
380399
if err != nil {
381-
//panic because this would really leave the app in an invalid state
400+
// panic because this would really leave the app in an invalid state
382401
panic(err)
383402
}
384403

@@ -445,12 +464,20 @@ func (s *Sqlite) runMigrations(ctx context.Context) error {
445464
source_environment_key text NOT NULL,
446465
context text NOT NULL,
447466
last_sync_time timestamp NOT NULL,
448-
flag_state TEXT NOT NULL
467+
flag_state TEXT NOT NULL,
468+
payload_version INTEGER NOT NULL DEFAULT 1
449469
)`)
450470
if err != nil {
451471
return err
452472
}
453473

474+
// Migration: add payload_version to existing databases that predate this column.
475+
_, err = tx.Exec(`ALTER TABLE projects ADD COLUMN payload_version INTEGER NOT NULL DEFAULT 1`)
476+
if err != nil && !strings.Contains(err.Error(), "duplicate column name") {
477+
return err
478+
}
479+
err = nil
480+
454481
_, err = tx.Exec(`
455482
CREATE TABLE IF NOT EXISTS overrides (
456483
project_key text NOT NULL,

internal/dev_server/db/sqlite_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func TestDBFunctions(t *testing.T) {
3636
SourceEnvironmentKey: "env-1",
3737
Context: ldContext,
3838
LastSyncTime: now,
39+
PayloadVersion: 1,
3940
AllFlagsState: model.FlagsState{
4041
"flag-1": model.FlagState{Value: ldvalue.Bool(true), Version: 2},
4142
"flag-2": model.FlagState{Value: ldvalue.String("cool"), Version: 2},
@@ -71,6 +72,7 @@ func TestDBFunctions(t *testing.T) {
7172
SourceEnvironmentKey: "env-2",
7273
Context: ldContext,
7374
LastSyncTime: now,
75+
PayloadVersion: 1,
7476
AllFlagsState: model.FlagsState{
7577
"flag-1": model.FlagState{Value: ldvalue.Int(123), Version: 2},
7678
"flag-2": model.FlagState{Value: ldvalue.Float64(99.99), Version: 2},
@@ -97,6 +99,7 @@ func TestDBFunctions(t *testing.T) {
9799
SourceEnvironmentKey: "env-3",
98100
Context: ldContext,
99101
LastSyncTime: now,
102+
PayloadVersion: 1,
100103
AllFlagsState: model.FlagsState{
101104
"flag-1": model.FlagState{Value: ldvalue.Int(123), Version: 2},
102105
"flag-2": model.FlagState{Value: ldvalue.Float64(99.99), Version: 2},
@@ -169,6 +172,7 @@ func TestDBFunctions(t *testing.T) {
169172
assert.Equal(t, expected.SourceEnvironmentKey, p.SourceEnvironmentKey)
170173
assert.Equal(t, expected.Context, p.Context)
171174
assert.True(t, expected.LastSyncTime.Equal(p.LastSyncTime))
175+
assert.Equal(t, expected.PayloadVersion, p.PayloadVersion)
172176
})
173177

174178
t.Run("GetAvailableVariations returns variations", func(t *testing.T) {
@@ -364,6 +368,25 @@ func TestDBFunctions(t *testing.T) {
364368
assert.True(t, found)
365369
})
366370

371+
t.Run("IncrementProjectPayloadVersion increments and returns new version", func(t *testing.T) {
372+
proj, err := store.GetDevProject(ctx, projects[0].Key)
373+
require.NoError(t, err)
374+
initialVersion := proj.PayloadVersion
375+
376+
newVersion, err := store.IncrementProjectPayloadVersion(ctx, projects[0].Key)
377+
require.NoError(t, err)
378+
assert.Equal(t, initialVersion+1, newVersion)
379+
380+
proj, err = store.GetDevProject(ctx, projects[0].Key)
381+
require.NoError(t, err)
382+
assert.Equal(t, initialVersion+1, proj.PayloadVersion)
383+
384+
// Calling again should increment once more
385+
newVersion2, err := store.IncrementProjectPayloadVersion(ctx, projects[0].Key)
386+
require.NoError(t, err)
387+
assert.Equal(t, initialVersion+2, newVersion2)
388+
})
389+
367390
t.Run("UpdateProject deletes overrides for flags that are no longer in the project", func(t *testing.T) {
368391
project := projects[2]
369392

internal/dev_server/model/import_project.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ func ImportProject(ctx context.Context, projectKey string, importData ImportData
5353
Context: importData.Context,
5454
AllFlagsState: importData.FlagsState,
5555
AvailableVariations: []FlagVariation{},
56+
PayloadVersion: 1,
5657
}
5758

5859
// Convert available variations if present

internal/dev_server/model/mocks/store.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/dev_server/model/override.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package model
33
import (
44
"context"
55

6+
"github.com/pkg/errors"
7+
68
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
79
)
810

@@ -58,6 +60,11 @@ func UpsertOverride(ctx context.Context, projectKey, flagKey string, value ldval
5860
return Override{}, err
5961
}
6062

63+
_, err = store.IncrementProjectPayloadVersion(ctx, projectKey)
64+
if err != nil {
65+
return Override{}, errors.Wrap(err, "unable to increment payload version")
66+
}
67+
6168
GetObserversFromContext(ctx).Notify(OverrideEvent{
6269
FlagKey: flagKey,
6370
ProjectKey: projectKey,
@@ -76,6 +83,12 @@ func DeleteOverride(ctx context.Context, projectKey, flagKey string) error {
7683
if err != nil {
7784
return err
7885
}
86+
87+
_, err = store.IncrementProjectPayloadVersion(ctx, projectKey)
88+
if err != nil {
89+
return errors.Wrap(err, "unable to increment payload version")
90+
}
91+
7992
override := Override{
8093
ProjectKey: projectKey,
8194
FlagKey: flagKey,

internal/dev_server/model/override_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func TestUpsertOverride(t *testing.T) {
7373
t.Run("override is applied, observers are notified", func(t *testing.T) {
7474
store.EXPECT().GetDevProject(gomock.Any(), projKey).Return(project, nil)
7575
store.EXPECT().UpsertOverride(gomock.Any(), override).Return(override, nil)
76+
store.EXPECT().IncrementProjectPayloadVersion(gomock.Any(), projKey).Return(1, nil)
7677
observer.
7778
EXPECT().
7879
Handle(model.OverrideEvent{
@@ -128,6 +129,7 @@ func TestDeleteOverride(t *testing.T) {
128129
t.Run("override is applied, observers are notified", func(t *testing.T) {
129130
store.EXPECT().GetDevProject(gomock.Any(), projKey).Return(project, nil)
130131
store.EXPECT().DeactivateOverride(gomock.Any(), projKey, flagKey).Return(2, nil)
132+
store.EXPECT().IncrementProjectPayloadVersion(gomock.Any(), projKey).Return(1, nil)
131133
observer.
132134
EXPECT().
133135
Handle(model.OverrideEvent{
@@ -198,11 +200,13 @@ func TestDeleteOverrides(t *testing.T) {
198200
// Expectations for first override
199201
store.EXPECT().GetDevProject(gomock.Any(), projKey).Return(project, nil)
200202
store.EXPECT().DeactivateOverride(gomock.Any(), projKey, flagKey).Return(2, nil)
203+
store.EXPECT().IncrementProjectPayloadVersion(gomock.Any(), projKey).Return(1, nil)
201204
observer.EXPECT().Handle(gomock.Any())
202205

203206
// Expectations for second override
204207
store.EXPECT().GetDevProject(gomock.Any(), projKey).Return(project, nil)
205208
store.EXPECT().DeactivateOverride(gomock.Any(), projKey, "flag2").Return(2, nil)
209+
store.EXPECT().IncrementProjectPayloadVersion(gomock.Any(), projKey).Return(2, nil)
206210
observer.EXPECT().Handle(gomock.Any())
207211

208212
err := model.DeleteOverrides(ctx, projKey)

internal/dev_server/model/project.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,15 @@ type Project struct {
1818
LastSyncTime time.Time
1919
AllFlagsState FlagsState
2020
AvailableVariations []FlagVariation
21+
PayloadVersion int
2122
}
2223

2324
// CreateProject creates a project and adds it to the database.
2425
func CreateProject(ctx context.Context, projectKey, sourceEnvironmentKey string, ldCtx *ldcontext.Context) (Project, error) {
2526
project := Project{
2627
Key: projectKey,
2728
SourceEnvironmentKey: sourceEnvironmentKey,
29+
PayloadVersion: 1,
2830
}
2931

3032
if ldCtx == nil {
@@ -87,6 +89,12 @@ func UpdateProject(ctx context.Context, projectKey string, context *ldcontext.Co
8789
return Project{}, errors.New("Project not updated")
8890
}
8991

92+
newPayloadVersion, err := store.IncrementProjectPayloadVersion(ctx, projectKey)
93+
if err != nil {
94+
return Project{}, errors.Wrap(err, "unable to increment payload version")
95+
}
96+
project.PayloadVersion = newPayloadVersion
97+
9098
allFlagsWithOverrides, err := project.GetFlagStateWithOverridesForProject(ctx)
9199
if err != nil {
92100
return Project{}, errors.Wrapf(err, "unable to get overrides for project, %s", projectKey)

internal/dev_server/model/project_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ func TestUpdateProject(t *testing.T) {
183183
sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), "sdkKey").Return(allFlagsState, nil)
184184
api.EXPECT().GetAllFlags(gomock.Any(), proj.Key).Return(allFlags, nil)
185185
store.EXPECT().UpdateProject(gomock.Any(), gomock.Any()).Return(true, nil)
186+
store.EXPECT().IncrementProjectPayloadVersion(gomock.Any(), proj.Key).Return(2, nil)
186187
store.EXPECT().GetOverridesForProject(gomock.Any(), proj.Key).Return(model.Overrides{}, nil)
187188
observer.
188189
EXPECT().
@@ -193,7 +194,9 @@ func TestUpdateProject(t *testing.T) {
193194

194195
project, err := model.UpdateProject(ctx, proj.Key, nil, nil)
195196
require.Nil(t, err)
196-
assert.Equal(t, proj, project)
197+
expectedProj := proj
198+
expectedProj.PayloadVersion = 2
199+
assert.Equal(t, expectedProj, project)
197200
})
198201
}
199202

internal/dev_server/model/store.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ type Store interface {
2828
UpsertOverride(ctx context.Context, override Override) (Override, error)
2929
GetOverridesForProject(ctx context.Context, projectKey string) (Overrides, error)
3030
GetAvailableVariationsForProject(ctx context.Context, projectKey string) (map[string][]Variation, error)
31+
// IncrementProjectPayloadVersion atomically increments the payload version for the project and returns the new version.
32+
IncrementProjectPayloadVersion(ctx context.Context, projectKey string) (int, error)
3133

3234
CreateBackup(ctx context.Context) (io.ReadCloser, int64, error)
3335
RestoreBackup(ctx context.Context, stream io.Reader) (string, error)

internal/dev_server/model/sync_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,8 +153,9 @@ func TestInitialSync(t *testing.T) {
153153
sdk.EXPECT().GetAllFlagsState(gomock.Any(), gomock.Any(), sdkKey).Return(allFlagsState, nil)
154154
api.EXPECT().GetAllFlags(gomock.Any(), projKey).Return(allFlags, nil)
155155
store.EXPECT().InsertProject(gomock.Any(), gomock.Any()).Return(nil)
156-
store.EXPECT().UpsertOverride(gomock.Any(), override).Return(override, nil)
157156
store.EXPECT().GetDevProject(gomock.Any(), projKey).Return(&proj, nil)
157+
store.EXPECT().UpsertOverride(gomock.Any(), override).Return(override, nil)
158+
store.EXPECT().IncrementProjectPayloadVersion(gomock.Any(), projKey).Return(1, nil)
158159

159160
input := model.InitialProjectSettings{
160161
Enabled: true,

0 commit comments

Comments
 (0)