Skip to content

Commit c2a8635

Browse files
committed
merge: fix/sqlite-created-by — created_by column in SQLite schema for PG parity
2 parents 766990b + bb1c998 commit c2a8635

2 files changed

Lines changed: 53 additions & 3 deletions

File tree

internal/store/store.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ func (s *SQLiteStore) migrate() error {
297297
created_at TEXT NOT NULL DEFAULT (datetime('now')),
298298
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
299299
deleted_at TEXT,
300+
created_by TEXT,
300301
FOREIGN KEY (session_id) REFERENCES sessions(id)
301302
);
302303
@@ -386,6 +387,7 @@ func (s *SQLiteStore) migrate() error {
386387
{name: "last_seen_at", definition: "TEXT"},
387388
{name: "updated_at", definition: "TEXT NOT NULL DEFAULT ''"},
388389
{name: "deleted_at", definition: "TEXT"},
390+
{name: "created_by", definition: "TEXT"},
389391
}
390392
for _, c := range observationColumns {
391393
if err := s.addColumnIfNotExists("observations", c.name, c.definition); err != nil {
@@ -3078,6 +3080,11 @@ func (s *SQLiteStore) migrateLegacyObservationsTable() error {
30783080
return nil
30793081
}
30803082

3083+
// Legacy tables predate created_by; ensure it exists before the copy below reads it.
3084+
if err := s.addColumnIfNotExists("observations", "created_by", "TEXT"); err != nil {
3085+
return err
3086+
}
3087+
30813088
tx, err := s.beginTxHook()
30823089
if err != nil {
30833090
return fmt.Errorf("migrate legacy observations: begin tx: %w", err)
@@ -3103,6 +3110,7 @@ func (s *SQLiteStore) migrateLegacyObservationsTable() error {
31033110
created_at TEXT NOT NULL DEFAULT (datetime('now')),
31043111
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
31053112
deleted_at TEXT,
3113+
created_by TEXT,
31063114
FOREIGN KEY (session_id) REFERENCES sessions(id)
31073115
);
31083116
`); err != nil {
@@ -3113,7 +3121,7 @@ func (s *SQLiteStore) migrateLegacyObservationsTable() error {
31133121
INSERT INTO observations_migrated (
31143122
id, sync_id, session_id, type, title, content, tool_name, project,
31153123
scope, topic_key, normalized_hash, revision_count, duplicate_count,
3116-
last_seen_at, created_at, updated_at, deleted_at
3124+
last_seen_at, created_at, updated_at, deleted_at, created_by
31173125
)
31183126
SELECT
31193127
CASE
@@ -3136,7 +3144,8 @@ func (s *SQLiteStore) migrateLegacyObservationsTable() error {
31363144
last_seen_at,
31373145
COALESCE(NULLIF(created_at, ''), datetime('now')),
31383146
COALESCE(NULLIF(updated_at, ''), NULLIF(created_at, ''), datetime('now')),
3139-
deleted_at
3147+
deleted_at,
3148+
created_by
31403149
FROM observations
31413150
ORDER BY rowid;
31423151
`); err != nil {
@@ -3401,7 +3410,8 @@ func (s *SQLiteStore) PromoteObservation(id int64, identity string) error {
34013410
}
34023411

34033412
// ListContributors returns contributor activity stats from observations and prompts.
3404-
// SQLite stub — does not have per-row created_by in all builds, returns empty.
3413+
// SQLite — created_by is only populated on rows synced from a multi-user backend;
3414+
// local-only writes leave it NULL, so this typically returns empty.
34053415
func (s *SQLiteStore) ListContributors(project string) ([]ContributorStats, error) {
34063416
obsQuery := `
34073417
SELECT COALESCE(created_by, '') as identity,

internal/store/store_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4293,6 +4293,46 @@ func TestListProjectsWithStats(t *testing.T) {
42934293
}
42944294
}
42954295

4296+
// Regression: ListProjects and ListContributors reference created_by, which the
4297+
// SQLite schema did not create — both failed with "no such column: o.created_by".
4298+
func TestListProjectsAndContributorsCreatedBy(t *testing.T) {
4299+
s := newTestStore(t)
4300+
4301+
if err := s.CreateSession("s1", "proj-a", "/work/a"); err != nil {
4302+
t.Fatalf("create session: %v", err)
4303+
}
4304+
if _, err := s.AddObservation(AddObservationParams{
4305+
SessionID: "s1",
4306+
Type: "decision",
4307+
Title: "obs a",
4308+
Content: "content",
4309+
Project: "proj-a",
4310+
Scope: "project",
4311+
}); err != nil {
4312+
t.Fatalf("AddObservation: %v", err)
4313+
}
4314+
4315+
projects, err := s.ListProjects(false)
4316+
if err != nil {
4317+
t.Fatalf("ListProjects: %v", err)
4318+
}
4319+
if len(projects) != 1 || projects[0].Project != "proj-a" {
4320+
t.Fatalf("expected [proj-a], got %+v", projects)
4321+
}
4322+
if projects[0].Observations != 1 {
4323+
t.Errorf("proj-a: expected 1 observation, got %d", projects[0].Observations)
4324+
}
4325+
4326+
// Local writes leave created_by NULL — contributors must be empty, not error.
4327+
contributors, err := s.ListContributors("")
4328+
if err != nil {
4329+
t.Fatalf("ListContributors: %v", err)
4330+
}
4331+
if len(contributors) != 0 {
4332+
t.Errorf("expected no contributors for local-only rows, got %+v", contributors)
4333+
}
4334+
}
4335+
42964336
func TestMergeProjects(t *testing.T) {
42974337
s := newTestStore(t)
42984338

0 commit comments

Comments
 (0)