Add versioning, update tool, and soft-delete

Schema: public_id + version columns with UNIQUE constraint,
delete_timestamp for soft-delete.

Tools: store_flashcard accepts optional public_id, update_flashcard
creates a new version preserving history, delete_flashcard inserts
a tombstone row and removes the Qdrant point.

Qdrant: point ID is public_id (only latest version indexed).
This commit is contained in:
didier committed 2026-06-03 20:15:18 -04:00
1 parent 726ab62c62
commit e700a81c25
10 files changed
+352 -72

No files matched your search

+91 -8
View File
@@ -42,14 +42,14 @@ func newSqliteStore(path string) (*sqliteStore, error) {
return &sqliteStore{db: db}, nil
}
func (s *sqliteStore) save(ctx context.Context, note, host, user, context_ string, repos, tags []string) (int64, error) {
func (s *sqliteStore) save(ctx context.Context, note, host, user, context_ string, repos, tags []string, publicID int64) (int64, error) {
reposJSON := marshalStringSlice(repos)
tagsJSON := marshalStringSlice(tags)
result, err := s.db.ExecContext(ctx,
`INSERT INTO knowledge (note, repos, host, tags, "user", context)
VALUES (?, ?, ?, ?, ?, ?)`,
note, reposJSON, host, tagsJSON, user, context_,
`INSERT INTO flashcard (public_id, version, note, repos, host, tags, "user", context)
VALUES (?, 1, ?, ?, ?, ?, ?, ?)`,
publicID, note, reposJSON, host, tagsJSON, user, context_,
)
if err != nil {
return 0, fmt.Errorf("insert: %w", err)
@@ -60,20 +60,27 @@ func (s *sqliteStore) save(ctx context.Context, note, host, user, context_ strin
return 0, fmt.Errorf("last insert id: %w", err)
}
if publicID == 0 {
_, err = s.db.ExecContext(ctx, `UPDATE flashcard SET public_id = ? WHERE _id = ?`, id, id)
if err != nil {
return 0, fmt.Errorf("set public_id: %w", err)
}
}
return id, nil
}
func (s *sqliteStore) getByID(ctx context.Context, id int64) (*entry, error) {
row := s.db.QueryRowContext(ctx,
`SELECT id, note, repos, host, tags, "user", context, created
FROM knowledge WHERE id = ?`, id,
`SELECT _id, public_id, version, note, repos, host, tags, "user", context, delete_timestamp, created
FROM flashcard WHERE _id = ?`, id,
)
var e entry
var reposJSON, tagsJSON sql.NullString
var host, user, context_ sql.NullString
var host, user, context_, deleteTimestamp sql.NullString
err := row.Scan(&e.ID, &e.Note, &reposJSON, &host, &tagsJSON, &user, &context_, &e.Created)
err := row.Scan(&e.ID, &e.PublicID, &e.Version, &e.Note, &reposJSON, &host, &tagsJSON, &user, &context_, &deleteTimestamp, &e.Created)
if err == sql.ErrNoRows {
return nil, nil
}
@@ -96,10 +103,71 @@ func (s *sqliteStore) getByID(ctx context.Context, id int64) (*entry, error) {
if context_.Valid {
e.Context = context_.String
}
if deleteTimestamp.Valid {
e.DeleteTimestamp = deleteTimestamp.String
}
return &e, nil
}
func (s *sqliteStore) getLatestByPublicID(ctx context.Context, publicID int64) (*entry, error) {
row := s.db.QueryRowContext(ctx,
`SELECT _id, public_id, version, note, repos, host, tags, "user", context, delete_timestamp, created
FROM flashcard WHERE public_id = ? ORDER BY version DESC LIMIT 1`, publicID,
)
var e entry
var reposJSON, tagsJSON sql.NullString
var host, user, context_, deleteTimestamp sql.NullString
err := row.Scan(&e.ID, &e.PublicID, &e.Version, &e.Note, &reposJSON, &host, &tagsJSON, &user, &context_, &deleteTimestamp, &e.Created)
if err == sql.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("query: %w", err)
}
if reposJSON.Valid {
e.Repos = unmarshalStringSlice(reposJSON.String)
}
if tagsJSON.Valid {
e.Tags = unmarshalStringSlice(tagsJSON.String)
}
if host.Valid {
e.Host = host.String
}
if user.Valid {
e.User = user.String
}
if context_.Valid {
e.Context = context_.String
}
if deleteTimestamp.Valid {
e.DeleteTimestamp = deleteTimestamp.String
}
return &e, nil
}
func (s *sqliteStore) tombstone(ctx context.Context, publicID int64, version int) (int64, error) {
result, err := s.db.ExecContext(ctx,
`INSERT INTO flashcard (public_id, version, note, delete_timestamp)
VALUES (?, ?, '', datetime('now'))`,
publicID, version,
)
if err != nil {
return 0, fmt.Errorf("tombstone: %w", err)
}
id, err := result.LastInsertId()
if err != nil {
return 0, fmt.Errorf("last insert id: %w", err)
}
return id, nil
}
func (s *sqliteStore) close() error {
return s.db.Close()
}
@@ -146,3 +214,18 @@ func parseOptionalString(raw any) string {
}
return ""
}
func parseInt64(raw any) int64 {
if raw == nil {
return 0
}
switch v := raw.(type) {
case float64:
return int64(v)
case int64:
return v
case int:
return int64(v)
}
return 0
}